* @author Joe Lapp * @author Dave Doyle */ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); require_once(DOKU_INC.'inc/search.php'); require_once(DOKU_INC.'inc/JpegMeta.php'); class syntax_plugin_gallery extends DokuWiki_Syntax_Plugin { /** * What kind of syntax are we? */ function getType(){ return 'substition'; } /** * What about paragraphs? */ function getPType(){ return 'block'; } /** * Where to sort in? */ function getSort(){ return 301; } /** * Connect pattern to lexer */ function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{gallery>[^}]*\}\}',$mode,'plugin_gallery'); } /** * Handle the match */ function handle($match, $state, $pos, &$handler){ global $ID; $match = substr($match,10,-2); //strip markup from start and end $data = array(); $data['galid'] = substr(md5($match),0,4); // alignment $data['align'] = 0; if(substr($match,0,1) == ' ') $data['align'] += 1; if(substr($match,-1,1) == ' ') $data['align'] += 2; // extract params list($ns,$params) = explode('?',$match,2); $ns = trim($ns); // namespace (including resolving relatives) if(!preg_match('/^https?:\/\//i',$ns)){ $data['ns'] = resolve_id(getNS($ID),$ns); }else{ $data['ns'] = $ns; } // set the defaults $data['tw'] = $this->getConf('thumbnail_width'); $data['th'] = $this->getConf('thumbnail_height'); $data['iw'] = $this->getConf('image_width'); $data['ih'] = $this->getConf('image_height'); $data['cols'] = $this->getConf('cols'); $data['filter'] = ''; $data['lightbox'] = false; $data['direct'] = false; $data['showname'] = false; $data['showtitle'] = false; $data['reverse'] = false; $data['random'] = false; $data['cache'] = true; $data['crop'] = false; $data['recursive']= true; $data['sort'] = $this->getConf('sort'); $data['limit'] = 0; $data['offset'] = 0; $data['paginate'] = 0; // parse additional options $params = $this->getConf('options').','.$params; $params = preg_replace('/[,&\?]+/',' ',$params); $params = explode(' ',$params); foreach($params as $param){ if($param === '') continue; if($param == 'titlesort'){ $data['sort'] = 'title'; }elseif($param == 'datesort'){ $data['sort'] = 'date'; }elseif($param == 'modsort'){ $data['sort'] = 'mod'; }elseif(preg_match('/^=(\d+)$/',$param,$match)){ $data['limit'] = $match[1]; }elseif(preg_match('/^\+(\d+)$/',$param,$match)){ $data['offset'] = $match[1]; }elseif(is_numeric($param)){ $data['cols'] = (int) $param; }elseif(preg_match('/^~(\d+)$/',$param,$match)){ $data['paginate'] = $match[1]; }elseif(preg_match('/^(\d+)([xX])(\d+)$/',$param,$match)){ if($match[2] == 'X'){ $data['iw'] = $match[1]; $data['ih'] = $match[3]; }else{ $data['tw'] = $match[1]; $data['th'] = $match[3]; } }elseif(strpos($param,'*') !== false){ $param = preg_quote($param,'/'); $param = '/^'.str_replace('\\*','.*?',$param).'$/'; $data['filter'] = $param; }else{ if(substr($param,0,2) == 'no'){ $data[substr($param,2)] = false; }else{ $data[$param] = true; } } } // implicit direct linking? if($data['lightbox']) $data['direct'] = true; return $data; } /** * Create output */ function render($mode, &$R, $data){ global $ID; if($mode == 'xhtml'){ $R->info['cache'] = $data['cache']; $R->doc .= $this->_gallery($data); return true; }elseif($mode == 'metadata'){ $rel = p_get_metadata($ID,'relation',METADATA_RENDER_USING_CACHE); $img = $rel['firstimage']; if(empty($img)){ $files = $this->_findimages($data); if(count($files)) $R->internalmedia($files[0]['id']); } return true; } return false; } /** * Loads images from a MediaRSS or ATOM feed */ function _loadRSS($url){ require_once(DOKU_INC.'inc/FeedParser.php'); $feed = new FeedParser(); $feed->set_feed_url($url); $feed->init(); $files = array(); // base url to use for broken feeds with non-absolute links $main = parse_url($url); $host = $main['scheme'].'://'. $main['host']. (($main['port'])?':'.$main['port']:''); $path = dirname($main['path']).'/'; foreach($feed->get_items() as $item){ if ($enclosure = $item->get_enclosure()){ // skip non-image enclosures if($enclosure->get_type()){ if(substr($enclosure->get_type(),0,5) != 'image') continue; }else{ if(!preg_match('/\.(jpe?g|png|gif)(\?|$)/i', $enclosure->get_link())) continue; } // non absolute links $ilink = $enclosure->get_link(); if(!preg_match('/^https?:\/\//i',$ilink)){ if($ilink{0} == '/'){ $ilink = $host.$ilink; }else{ $ilink = $host.$path.$ilink; } } $link = $item->link; if(!preg_match('/^https?:\/\//i',$link)){ if($link{0} == '/'){ $link = $host.$link; }else{ $link = $host.$path.$link; } } $files[] = array( 'id' => $ilink, 'isimg' => true, 'file' => basename($ilink), // decode to avoid later double encoding 'title' => htmlspecialchars_decode($enclosure->get_title(),ENT_COMPAT), 'desc' => strip_tags(htmlspecialchars_decode($enclosure->get_description(),ENT_COMPAT)), 'width' => $enclosure->get_width(), 'height' => $enclosure->get_height(), 'mtime' => $item->get_date('U'), 'ctime' => $item->get_date('U'), 'detail' => $link, ); } } return $files; } /** * Gather all photos matching the given criteria */ function _findimages(&$data){ global $conf; $files = array(); // http URLs are supposed to be media RSS feeds if(preg_match('/^https?:\/\//i',$data['ns'])){ $files = $this->_loadRSS($data['ns']); $data['_single'] = false; }else{ $dir = utf8_encodeFN(str_replace(':','/',$data['ns'])); // all possible images for the given namespace (or a single image) if(is_file($conf['mediadir'].'/'.$dir)){ require_once(DOKU_INC.'inc/JpegMeta.php'); $files[] = array( 'id' => $data['ns'], 'isimg' => preg_match('/\.(jpe?g|gif|png)$/',$dir), 'file' => basename($dir), 'mtime' => filemtime($conf['mediadir'].'/'.$dir), 'meta' => new JpegMeta($conf['mediadir'].'/'.$dir) ); $data['_single'] = true; }else{ $depth = $data['recursive'] ? 0 : 1; search($files, $conf['mediadir'], 'search_media', array('depth'=>$depth), $dir); $data['_single'] = false; } } // done, yet? $len = count($files); if(!$len) return $files; if($data['single']) return $files; // filter images for($i=0; $i<$len; $i++){ if(!$files[$i]['isimg']){ unset($files[$i]); // this is faster, because RE was done before }elseif($data['filter']){ if(!preg_match($data['filter'],noNS($files[$i]['id']))) unset($files[$i]); } } if($len<1) return $files; // random? if($data['random']){ shuffle($files); }else{ // sort? if($data['sort'] == 'date'){ usort($files,array($this,'_datesort')); }elseif($data['sort'] == 'mod'){ usort($files,array($this,'_modsort')); }elseif($data['sort'] == 'title'){ usort($files,array($this,'_titlesort')); } // reverse? if($data['reverse']) $files = array_reverse($files); } // limits and offsets? if($data['offset']) $files = array_slice($files,$data['offset']); if($data['limit']) $files = array_slice($files,0,$data['limit']); return $files; } /** * usort callback to sort by file lastmodified time */ function _modsort($a,$b){ if($a['mtime'] < $b['mtime']) return -1; if($a['mtime'] > $b['mtime']) return 1; return strcmp($a['file'],$b['file']); } /** * usort callback to sort by EXIF date */ function _datesort($a,$b){ $da = $this->_meta($a,'cdate'); $db = $this->_meta($b,'cdate'); if($da < $db) return -1; if($da > $db) return 1; return strcmp($a['file'],$b['file']); } /** * usort callback to sort by EXIF title */ function _titlesort($a,$b){ $ta = $this->_meta($a,'title'); $tb = $this->_meta($b,'title'); return strcmp($ta,$tb); } /** * Does the gallery formatting */ function _gallery($data){ global $conf; global $lang; $ret = ''; $files = $this->_findimages($data); //anything found? if(!count($files)){ $ret .= '
'.$lang['nothingfound'].'
'; return $ret; } // prepare alignment $align = ''; $xalign = ''; if($data['align'] == 1){ $align = ' gallery_right'; $xalign = ' align="right"'; } if($data['align'] == 2){ $align = ' gallery_left'; $xalign = ' align="left"'; } if($data['align'] == 3){ $align = ' gallery_center'; $xalign = ' align="center"'; } if(!$data['_single']){ if(!$align) $align = ' gallery_center'; // center galleries on default if(!$xalign) $xalign = ' align="center"'; } $page = 0; // build gallery if($data['_single']){ $ret .= $this->_image($files[0],$data); $ret .= $this->_showname($files[0],$data); $ret .= $this->_showtitle($files[0],$data); }elseif($data['cols'] > 0){ // format as table $close_pg = false; $i = 0; foreach($files as $img){ // new page? if($data['paginate'] && ($i % $data['paginate'] == 0)){ $ret .= ''; $close_pg = false; } } if ($close_tr){ // add remaining empty cells while($i % $data['cols']){ $ret .= ''; $i++; } $ret .= ''; } if(!$data['paginate']){ $ret .= ''; }elseif ($close_pg){ $ret .= ''; $ret .= ''; } }else{ // format as div sequence $i = 0; $close_pg = false; foreach($files as $img){ if($data['paginate'] && ($i % $data['paginate'] == 0)){ $ret .= ''; $close_pg = false; } } if($close_pg) $ret .= ''; $ret .= '
'; } // pagination links $pgret = ''; if($page){ $pgret .= ''; } return '
'.$pgret.$ret.'
'; } /** * Defines how a thumbnail should look like */ function _image(&$img,$data){ global $ID; // calculate thumbnail size if(!$data['crop']){ $w = (int) $this->_meta($img,'width'); $h = (int) $this->_meta($img,'height'); if($w && $h){ $dim = array(); if($w > $data['tw'] || $h > $data['th']){ $ratio = $this->_ratio($img,$data['tw'],$data['th']); $w = floor($w * $ratio); $h = floor($h * $ratio); $dim = array('w'=>$w,'h'=>$h); } }else{ $data['crop'] = true; // no size info -> always crop } } if($data['crop']){ $w = $data['tw']; $h = $data['th']; $dim = array('w'=>$w,'h'=>$h); } //prepare img attributes $i = array(); $i['width'] = $w; $i['height'] = $h; $i['border'] = 0; $i['alt'] = $this->_meta($img,'title'); $i['longdesc'] = str_replace("\n",' ',$this->_meta($img,'desc')); if(!$i['longdesc']) unset($i['longdesc']); $i['class'] = 'tn'; $iatt = buildAttributes($i); $src = ml($img['id'],$dim); // prepare lightbox dimensions $w_lightbox = (int) $this->_meta($img,'width'); $h_lightbox = (int) $this->_meta($img,'height'); $dim_lightbox = array(); if($w_lightbox > $data['iw'] || $h_lightbox > $data['ih']){ $ratio = $this->_ratio($img,$data['iw'],$data['ih']); $w_lightbox = floor($w_lightbox * $ratio); $h_lightbox = floor($h_lightbox * $ratio); $dim_lightbox = array('w'=>$w_lightbox,'h'=>$h_lightbox); } //prepare link attributes $a = array(); $a['title'] = $this->_meta($img,'title'); if($data['lightbox']){ $href = ml($img['id'],$dim_lightbox); $a['class'] = "lightbox JSnocheck"; $a['rel'] = 'lightbox[gal-'.substr(md5($ID),4).']'; //unique ID for the gallery }elseif($img['detail'] && !$data['direct']){ $href = $img['detail']; }else{ $href = ml($img['id'],array('id'=>$ID),$data['direct']); } $aatt = buildAttributes($a); // prepare output $ret = ''; $ret .= ''; $ret .= ''; $ret .= ''; return $ret; } /** * Defines how a filename + link should look */ function _showname($img,$data){ global $ID; if(!$data['showname'] ) { return ''; } //prepare link $lnk = ml($img['id'],array('id'=>$ID),false); // prepare output $ret = ''; $ret .= '
'; $ret .= hsc($img['file']); $ret .= ''; return $ret; } /** * Defines how title + link should look */ function _showtitle($img,$data){ global $ID; if(!$data['showtitle'] ) { return ''; } //prepare link $lnk = ml($img['id'],array('id'=>$ID),false); // prepare output $ret = ''; $ret .= '
'; $ret .= hsc($this->_meta($img,'title')); $ret .= ''; return $ret; } /** * Return the metadata of an item * * Automatically checks if a JPEGMeta object is available or if all data is * supplied in array */ function _meta(&$img,$opt){ if($img['meta']){ // map JPEGMeta calls to opt names switch($opt){ case 'title': return $img['meta']->getField('Simple.Title'); case 'desc': return $img['meta']->getField('Iptc.Caption'); case 'cdate': return $img['meta']->getField('Date.EarliestTime'); case 'width': return $img['meta']->getField('File.Width'); case 'height': return $img['meta']->getField('File.Height'); default: return ''; } }else{ // just return the array field return $img[$opt]; } } /** * Calculates the multiplier needed to resize the image to the given * dimensions * * @author Andreas Gohr */ function _ratio(&$img,$maxwidth,$maxheight=0){ if(!$maxheight) $maxheight = $maxwidth; $w = $this->_meta($img,'width'); $h = $this->_meta($img,'height'); $ratio = 1; if($w >= $h){ if($w >= $maxwidth){ $ratio = $maxwidth/$w; }elseif($h > $maxheight){ $ratio = $maxheight/$h; } }else{ if($h >= $maxheight){ $ratio = $maxheight/$h; }elseif($w > $maxwidth){ $ratio = $maxwidth/$w; } } return $ratio; } } //Setup VIM: ex: et ts=4 enc=utf-8 :