127bbde00SAndreas Gohr<?php 2*8f5b2c44SAndreas Gohr 3*8f5b2c44SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin; 4*8f5b2c44SAndreas Gohr 527bbde00SAndreas Gohr/** 627bbde00SAndreas Gohr * Embed an image gallery 727bbde00SAndreas Gohr * 827bbde00SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 927bbde00SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1027bbde00SAndreas Gohr */ 11*8f5b2c44SAndreas Gohrclass syntax_plugin_panoview extends SyntaxPlugin 12*8f5b2c44SAndreas Gohr{ 1327bbde00SAndreas Gohr /** 1427bbde00SAndreas Gohr * What kind of syntax are we? 1527bbde00SAndreas Gohr */ 16*8f5b2c44SAndreas Gohr public function getType() 17*8f5b2c44SAndreas Gohr { 1827bbde00SAndreas Gohr return 'substition'; 1927bbde00SAndreas Gohr } 2027bbde00SAndreas Gohr 2127bbde00SAndreas Gohr /** 2227bbde00SAndreas Gohr * What about paragraphs? 2327bbde00SAndreas Gohr */ 24*8f5b2c44SAndreas Gohr public function getPType() 25*8f5b2c44SAndreas Gohr { 2627bbde00SAndreas Gohr return 'block'; 2727bbde00SAndreas Gohr } 2827bbde00SAndreas Gohr 2927bbde00SAndreas Gohr /** 3027bbde00SAndreas Gohr * Where to sort in? 3127bbde00SAndreas Gohr */ 32*8f5b2c44SAndreas Gohr public function getSort() 33*8f5b2c44SAndreas Gohr { 3427bbde00SAndreas Gohr return 301; 3527bbde00SAndreas Gohr } 3627bbde00SAndreas Gohr 3727bbde00SAndreas Gohr /** 3827bbde00SAndreas Gohr * Connect pattern to lexer 3927bbde00SAndreas Gohr */ 40*8f5b2c44SAndreas Gohr public function connectTo($mode) 41*8f5b2c44SAndreas Gohr { 4227bbde00SAndreas Gohr $this->Lexer->addSpecialPattern('\{\{panoview>[^}]*\}\}', $mode, 'plugin_panoview'); 4327bbde00SAndreas Gohr } 4427bbde00SAndreas Gohr 4527bbde00SAndreas Gohr /** 4627bbde00SAndreas Gohr * Handle the match 4727bbde00SAndreas Gohr */ 48*8f5b2c44SAndreas Gohr public function handle($match, $state, $pos, Doku_Handler $handler) 49*8f5b2c44SAndreas Gohr { 5027bbde00SAndreas Gohr global $ID; 5127bbde00SAndreas Gohr 52*8f5b2c44SAndreas Gohr $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']; 5327bbde00SAndreas Gohr 541a675665SAndreas Gohr $match = substr($match, 11, -2); //strip markup from start and end 5527bbde00SAndreas Gohr 561a675665SAndreas Gohr // alignment 571a675665SAndreas Gohr $data['align'] = 0; 58*8f5b2c44SAndreas Gohr if (str_starts_with($match, ' ')) $data['align'] += 1; 59*8f5b2c44SAndreas Gohr if (str_ends_with($match, ' ')) $data['align'] += 2; 601a675665SAndreas Gohr 611a675665SAndreas Gohr // extract params 62*8f5b2c44SAndreas Gohr [$img, $params] = explode('?', $match, 2); 631a675665SAndreas Gohr $img = trim($img); 641a675665SAndreas Gohr 651a675665SAndreas Gohr // resolving relatives 661a675665SAndreas Gohr $data['image'] = resolve_id(getNS($ID), $img); 671a675665SAndreas Gohr 681a675665SAndreas Gohr $file = mediaFN($data['image']); 69*8f5b2c44SAndreas Gohr [$data['imageWidth'], $data['imageHeight']] = @getimagesize($file); 701a675665SAndreas Gohr 714212171fSAndreas Gohr // calculate maximum zoom 72359cb25cSAndreas Gohr $data['maxZoom'] = ceil(sqrt(max($data['imageWidth'], $data['imageHeight']) / $data['tileSize'])); 734212171fSAndreas Gohr 741a675665SAndreas Gohr // size 754212171fSAndreas Gohr if (preg_match('/\b(\d+)[xX](\d+)\b/', $params, $match)) { 761a675665SAndreas Gohr $data['width'] = $match[1]; 771a675665SAndreas Gohr $data['height'] = $match[2]; 781a675665SAndreas Gohr } 791a675665SAndreas Gohr 804212171fSAndreas Gohr // initial zoom 814212171fSAndreas Gohr if (preg_match('/\b[zZ](\d+)\b/', $params, $match)) { 824212171fSAndreas Gohr $data['initialZoom'] = $match[1]; 834212171fSAndreas Gohr } 844212171fSAndreas Gohr if ($data['initialZoom'] < 0) $data['initialZoom'] = 0; 8524e066d7SAndreas Gohr if ($data['initialZoom'] > $data['maxZoom']) $data['initialZoom'] = $data['maxZoom']; 864212171fSAndreas Gohr 871a675665SAndreas Gohr return $data; 8827bbde00SAndreas Gohr } 8927bbde00SAndreas Gohr 9027bbde00SAndreas Gohr /** 9127bbde00SAndreas Gohr * Create output 9227bbde00SAndreas Gohr */ 93*8f5b2c44SAndreas Gohr public function render($mode, Doku_Renderer $R, $data) 94*8f5b2c44SAndreas Gohr { 9527bbde00SAndreas Gohr if ($mode != 'xhtml') return false; 96da5b8f02SAndreas Gohr global $ID; 9727bbde00SAndreas Gohr 98*8f5b2c44SAndreas Gohr $img = '<a href="' . ml($data['image'], ['id' => $ID], false) . '"><img src="' . 99*8f5b2c44SAndreas Gohr ml($data['image'], ['w' => $data['width'], 'h' => $data['height']]) . '" width="' . 1001a675665SAndreas Gohr $data['width'] . '" height="' . $data['height'] . '" alt="" /></a>'; 10127bbde00SAndreas Gohr 1021613315fSAndreas Gohr if ($data['align'] == 1) { 1031613315fSAndreas Gohr $align = 'medialeft'; 1041613315fSAndreas Gohr } elseif ($data['align'] == 2) { 1051613315fSAndreas Gohr $align = 'mediaright'; 1061613315fSAndreas Gohr } else { 1071613315fSAndreas Gohr $align = 'mediacenter'; 1081613315fSAndreas Gohr } 1091613315fSAndreas Gohr 11027bbde00SAndreas Gohr $R->doc .= ' 1111613315fSAndreas Gohr <div class="panoview_plugin ' . $align . '" style="width: ' . $data['width'] . 'px; height: ' . $data['height'] . 'px;"> 1129d7db126SAndreas Gohr <div class="well"><!-- --></div> 1139d7db126SAndreas Gohr <div class="surface">' . $img . '</div> 11427bbde00SAndreas Gohr <p class="controls" style="display: none"> 11527bbde00SAndreas Gohr <span class="zoomIn" title="Zoom In">+</span> 11627bbde00SAndreas Gohr <span class="zoomOut" title="Zoom Out">-</span> 117493e6d40SAndreas Gohr <span class="maximize"><img src="' . DOKU_BASE . 'lib/plugins/panoview/gfx/window.gif" style="position: absolute; bottom: 4px; right: 5px;" title="Maximize"></span> 11827bbde00SAndreas Gohr </p> 1197242a9edSAndreas Gohr <div class="options" style="display:none">' . hsc(json_encode($data)) . '</div> 12027bbde00SAndreas Gohr </div> 12127bbde00SAndreas Gohr '; 12227bbde00SAndreas Gohr 12327bbde00SAndreas Gohr return true; 12427bbde00SAndreas Gohr } 12527bbde00SAndreas Gohr 1261faa1ff0SAndreas Gohr // ----------- Tile Generator below --------------- 1271faa1ff0SAndreas Gohr 1281faa1ff0SAndreas Gohr /** 1291faa1ff0SAndreas Gohr * Create a tile using libGD 1301faa1ff0SAndreas Gohr */ 131*8f5b2c44SAndreas Gohr public function tileGD($d) 132*8f5b2c44SAndreas Gohr { 1331faa1ff0SAndreas Gohr global $conf; 1341faa1ff0SAndreas Gohr 1351faa1ff0SAndreas Gohr $img = null; 1361faa1ff0SAndreas Gohr if (preg_match('/\.jpe?g$/', $d['file'])) { 1371faa1ff0SAndreas Gohr $img = @imagecreatefromjpeg($d['file']); 1381faa1ff0SAndreas Gohr } elseif (preg_match('/\.png$/', $d['file'])) { 1391faa1ff0SAndreas Gohr $img = @imagecreatefrompng($d['file']); 1401faa1ff0SAndreas Gohr } elseif (preg_match('/\.gif$/', $d['file'])) { 1411faa1ff0SAndreas Gohr $img = @imagecreatefromgif($d['file']); 1421faa1ff0SAndreas Gohr } 143*8f5b2c44SAndreas Gohr if ($img === null) $this->gfxError('generic'); 1441faa1ff0SAndreas Gohr 145*8f5b2c44SAndreas Gohr $crop = $this->imageCrop($img, $d['width'], $d['height'], $d['tlx'], $d['tly'], $d['brx'], $d['bry']); 1461faa1ff0SAndreas Gohr imagedestroy($img); 1471faa1ff0SAndreas Gohr 148*8f5b2c44SAndreas Gohr $scale = $this->imageScale($crop, abs($d['brx'] - $d['tlx']), abs($d['bry'] - $d['tly']), $d['ts'], $d['ts']); 1491faa1ff0SAndreas Gohr imagedestroy($crop); 1501faa1ff0SAndreas Gohr 1511faa1ff0SAndreas Gohr imagejpeg($scale, $d['cache'], $conf['jpg_quality']); 1521faa1ff0SAndreas Gohr imagedestroy($scale); 1531faa1ff0SAndreas Gohr 1541faa1ff0SAndreas Gohr if ($conf['fperm']) chmod($d['cache'], $conf['fperm']); 1551faa1ff0SAndreas Gohr } 1561faa1ff0SAndreas Gohr 1571faa1ff0SAndreas Gohr /** 1581faa1ff0SAndreas Gohr * Create a tile using Image Magick 1591faa1ff0SAndreas Gohr */ 160*8f5b2c44SAndreas Gohr public function tileIM($d) 161*8f5b2c44SAndreas Gohr { 1621faa1ff0SAndreas Gohr global $conf; 1631faa1ff0SAndreas Gohr 1641faa1ff0SAndreas Gohr $cmd = $this->getConf('nice'); 1651faa1ff0SAndreas Gohr $cmd .= ' ' . $conf['im_convert']; 1661faa1ff0SAndreas Gohr $cmd .= ' ' . escapeshellarg($d['file']); 167*8f5b2c44SAndreas Gohr $cmd .= ' -crop \'' . abs($d['brx'] - $d['tlx']) . 'x' . abs($d['bry'] - $d['tly']) . 168*8f5b2c44SAndreas Gohr '!+' . $d['tlx'] . '+' . $d['tly'] . '\''; 1691faa1ff0SAndreas Gohr $cmd .= ' -background black'; 1701faa1ff0SAndreas Gohr $cmd .= ' -extent \'' . abs($d['brx'] - $d['tlx']) . 'x' . abs($d['bry'] - $d['tly']) . '!\''; 1711faa1ff0SAndreas Gohr $cmd .= ' -resize \'' . $d['ts'] . 'x' . $d['ts'] . '!\''; 1721faa1ff0SAndreas Gohr 1731faa1ff0SAndreas Gohr $cmd .= ' -quality ' . $conf['jpg_quality']; 1741faa1ff0SAndreas Gohr $cmd .= ' ' . escapeshellarg($d['cache']); 1751faa1ff0SAndreas Gohr 1761faa1ff0SAndreas Gohr # dbg($cmd); exit; 1771faa1ff0SAndreas Gohr 1781faa1ff0SAndreas Gohr @exec($cmd, $out, $retval); 1791faa1ff0SAndreas Gohr if ($retval == 0) return true; 180*8f5b2c44SAndreas Gohr $this->gfxError('generic'); 1811faa1ff0SAndreas Gohr } 1821faa1ff0SAndreas Gohr 1831faa1ff0SAndreas Gohr /** 1841faa1ff0SAndreas Gohr * Scale an image with libGD 1851faa1ff0SAndreas Gohr */ 186*8f5b2c44SAndreas Gohr public function imageScale($image, $x, $y, $w, $h) 187*8f5b2c44SAndreas Gohr { 1881faa1ff0SAndreas Gohr $scale = imagecreatetruecolor($w, $h); 1891faa1ff0SAndreas Gohr imagecopyresampled($scale, $image, 0, 0, 0, 0, $w, $h, $x, $y); 1901faa1ff0SAndreas Gohr return $scale; 1911faa1ff0SAndreas Gohr } 1921faa1ff0SAndreas Gohr 1931faa1ff0SAndreas Gohr /** 1941faa1ff0SAndreas Gohr * Crop an image with libGD 1951faa1ff0SAndreas Gohr */ 196*8f5b2c44SAndreas Gohr public function imageCrop($image, $x, $y, $left, $upper, $right, $lower) 197*8f5b2c44SAndreas Gohr { 1981faa1ff0SAndreas Gohr $w = abs($right - $left); 1991faa1ff0SAndreas Gohr $h = abs($lower - $upper); 2001faa1ff0SAndreas Gohr $crop = imagecreatetruecolor($w, $h); 2011faa1ff0SAndreas Gohr imagecopy($crop, $image, 0, 0, $left, $upper, $w, $h); 2021faa1ff0SAndreas Gohr return $crop; 2031faa1ff0SAndreas Gohr } 2041faa1ff0SAndreas Gohr 2051faa1ff0SAndreas Gohr /** 2061faa1ff0SAndreas Gohr * Send a graphical error message and stop script 2071faa1ff0SAndreas Gohr */ 208*8f5b2c44SAndreas Gohr public function gfxError($type) 209*8f5b2c44SAndreas Gohr { 210*8f5b2c44SAndreas Gohr $file = __DIR__ . '/gfx/' . $type . '.gif'; 2111faa1ff0SAndreas Gohr $time = filemtime($file); 2121faa1ff0SAndreas Gohr header('Content-type: image/gif'); 2131faa1ff0SAndreas Gohr 2141faa1ff0SAndreas Gohr http_conditionalRequest($time); 2151faa1ff0SAndreas Gohr http_sendfile($file); 2161faa1ff0SAndreas Gohr readfile($file); 2171faa1ff0SAndreas Gohr exit; 2181faa1ff0SAndreas Gohr } 2191faa1ff0SAndreas Gohr 2201faa1ff0SAndreas Gohr /** 2211faa1ff0SAndreas Gohr * Acquire a lock for the tile generator 2221faa1ff0SAndreas Gohr */ 223*8f5b2c44SAndreas Gohr public function tileLock($d) 224*8f5b2c44SAndreas Gohr { 2251faa1ff0SAndreas Gohr global $conf; 2261faa1ff0SAndreas Gohr 2271faa1ff0SAndreas Gohr $lockDir = $conf['lockdir'] . '/' . md5($d['id']) . '.panoview'; 2281faa1ff0SAndreas Gohr @ignore_user_abort(1); 2291faa1ff0SAndreas Gohr 2301faa1ff0SAndreas Gohr $timeStart = time(); 2311faa1ff0SAndreas Gohr do { 2321faa1ff0SAndreas Gohr //waited longer than 25 seconds? -> stale lock? 2331faa1ff0SAndreas Gohr if ((time() - $timeStart) > 25) { 234*8f5b2c44SAndreas Gohr if (time() - @filemtime($lockDir) > 30) $this->tileUnlock($d); 235*8f5b2c44SAndreas Gohr send_redirect( 236*8f5b2c44SAndreas Gohr DOKU_URL . 'lib/plugins/panoview/tiles.php?tile=' . 237*8f5b2c44SAndreas Gohr $d['zoom'] . '-' . $d['col'] . '-' . $d['row'] . '&image=' . rawurlencode($d['id']) 238*8f5b2c44SAndreas Gohr ); 2391faa1ff0SAndreas Gohr exit; 2401faa1ff0SAndreas Gohr } 2411faa1ff0SAndreas Gohr $locked = @mkdir($lockDir, $conf['dmode']); 2421faa1ff0SAndreas Gohr if ($locked) { 2431faa1ff0SAndreas Gohr if (!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']); 2441faa1ff0SAndreas Gohr break; 2451faa1ff0SAndreas Gohr } 246*8f5b2c44SAndreas Gohr usleep(random_int(500, 3000)); 2471faa1ff0SAndreas Gohr } while ($locked === false); 2481faa1ff0SAndreas Gohr } 2491faa1ff0SAndreas Gohr 2501faa1ff0SAndreas Gohr /** 2511faa1ff0SAndreas Gohr * Unlock the tile generator 2521faa1ff0SAndreas Gohr */ 253*8f5b2c44SAndreas Gohr public function tileUnlock($d) 254*8f5b2c44SAndreas Gohr { 2551faa1ff0SAndreas Gohr global $conf; 2561faa1ff0SAndreas Gohr 2571faa1ff0SAndreas Gohr $lockDir = $conf['lockdir'] . '/' . md5($d['id']) . '.panoview'; 2581faa1ff0SAndreas Gohr @rmdir($lockDir); 2591faa1ff0SAndreas Gohr @ignore_user_abort(0); 2601faa1ff0SAndreas Gohr } 26127bbde00SAndreas Gohr} 262