1<?php
2/**
3 * DokuWiki Plugin photogallery (Syntax Component)
4 * Embed an image gallery
5 *
6 * @license      GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author       Marco Nolletti
8 * @contributors Michael Große
9 */
10
11 // must be run within Dokuwiki
12if(!defined('DOKU_INC')) die();
13
14require_once('inc/pgdefines.php');
15require_once(DOKU_PLUGIN.'syntax.php');
16require_once(DOKU_INC.'inc/search.php');
17require_once(DOKU_INC.'inc/JpegMeta.php');
18require_once('lib/array_column.php');
19
20class syntax_plugin_photogallery extends DokuWiki_Syntax_Plugin {
21
22    protected $metaAliases = null;
23
24    /**
25     * What kind of syntax are we?
26     */
27    function getType(){
28        return 'substition';
29    }
30
31    /**
32     * What about paragraphs?
33     */
34    function getPType(){
35        return 'block';
36    }
37
38    /**
39     * Where to sort in?
40     */
41    function getSort(){
42        return 155;
43    }
44
45    /**
46     * Connect pattern to lexer
47     */
48    function connectTo($mode) {
49				$this->Lexer->addSpecialPattern('----+ *photogallery(?: [ a-zA-Z0-9_]*)?-+\n.*?\n?----+', $mode, 'plugin_photogallery');
50    }
51
52    /**
53     * Handle the match - parse the data
54     *
55     * @param   string       $match   The text matched by the patterns
56     * @param   int          $state   The lexer state for the match
57     * @param   int          $pos     The character position of the matched text
58     * @param   Doku_Handler $handler The Doku_Handler object
59     * @return  bool|array Return an array with all data you want to use in render, false don't add an instruction
60     */
61    function handle($match, $state, $pos, Doku_Handler $handler){
62        global $ID;
63				global $conf;
64
65        $data = array();
66
67        // get lines
68        $lines = explode("\n", $match);
69        array_pop($lines);
70
71				// get command
72        $cmd = array_shift($lines);
73        $cmd = str_replace('photogallery', '', $cmd);
74        $cmd = trim($cmd, '- ');
75				if (!strpos('show|link',$cmd)) {
76						$cmd = 'show';
77				}
78				$data['command'] = $cmd;
79
80        // set the defaults
81        $data['phpthumb']    = $this->getConf('use_phpThumb');
82        $data['autoplay']    = $this->getConf('autoplay');
83        $data['pw']          = $this->getConf('poster_width');
84        $data['ph']          = $this->getConf('poster_height');
85        $data['tw']          = $this->getConf('thumbnail_width');
86        $data['th']          = $this->getConf('thumbnail_height');
87        $data['iw']          = $this->getConf('viewport_width');
88        $data['ih']          = $this->getConf('viewport_height');
89        $data['vprot']       = $this->getConf('viewport_rotate');
90        $data['panar']       = $this->getConf('panorama_ratio');
91        $data['panw']        = $this->getConf('panorama_width');
92        $data['panh']        = $this->getConf('panorama_height');
93        $data['posteralign'] = $this->getConf('posteralign');
94        $data['filter']      = '';
95        $data['fullsize']    = $this->getConf('fullsize');
96        $data['sort']        = $this->getConf('sort');
97        $data['limit']       = 0;
98        $data['offset']      = 0;
99        //$data['fullsize']    = 0;
100				$data['ns']          = getNS($ID);
101				$this->_setConfOptions($data,$this->getConf('options'));
102
103        // parse additional options
104        $params = $this->getConf('options').','.$params;
105        $params = preg_replace('/[,&\?]+/',' ',$params);
106        $params = explode(' ',$params);
107        foreach($params as $param){
108            if($param === '') continue;
109            if($param == 'titlesort'){
110                $data['sort'] = 'title';
111            }elseif($param == 'datesort'){
112                $data['sort'] = 'date';
113            }elseif($param == 'modsort'){
114                $data['sort'] = 'mod';
115            }elseif(preg_match('/^=(\d+)$/',$param,$match)){
116                $data['limit'] = $match[1];
117            }elseif(preg_match('/^\+(\d+)$/',$param,$match)){
118                $data['offset'] = $match[1];
119            }elseif(is_numeric($param)){
120                $data['cols'] = (int) $param;
121            }elseif(preg_match('/^(\d+)([xX])(\d+)$/',$param,$match)){
122                if($match[2] == 'X'){
123                    $data['iw'] = $match[1];
124                    $data['ih'] = $match[3];
125                }else{
126                    $data['tw'] = $match[1];
127                    $data['th'] = $match[3];
128                }
129            }elseif(strpos($param,'*') !== false){
130                $param = preg_quote($param,'/');
131                $param = '/^'.str_replace('\\*','.*?',$param).'$/';
132                $data['filter'] = $param;
133            }else{
134                if(substr($param,0,2) == 'no'){
135                    $data[substr($param,2)] = false;
136                }else{
137                    $data[$param] = true;
138                }
139            }
140        }
141
142				// Check phpThumb requirements
143				if ($data['phpthumb'] == true){
144						if (!$this->_phpThumbCheck()){
145								msg($this->getLang('phpthumbdisabled'),2);
146								$data['phpthumb'] = false;
147						}
148				}
149
150        // parse info
151        foreach($lines as $line) {
152            // ignore comments
153            preg_match('/^(.*?(?<![&\\\\]))(?:#(.*))?$/', $line, $matches);
154            $line = $matches[1];
155            $line = str_replace('\\#', '#', $line);
156            $line = trim($line);
157            if(empty($line)) continue;
158            $line = preg_split('/\s*:\s*/', $line, 2);
159//            $line = preg_split('/*:*/', $line, 1);
160						if($line[0] == 'namespace') $line[0] = 'ns';
161						if($line[0] == 'page') $line[0] = 'pg';
162
163						if($line[0] == 'ns'){
164								if(preg_match('/^https?:\/\//i',$line[1]))
165										$data['rss'] = true;
166								else
167										$line[1] = resolve_id(getNS($ID),$line[1]);
168						}
169						if($line[0] == 'pg'){
170								$line[1] = resolve_id(getNS($ID),$line[1]);
171						}
172
173						// decode height x width values [pti]size strings
174						if(preg_match('/^([pti])(size)$/',$line[0],$type)){
175								if(preg_match('/^(\d+)([xX])(\d+)$/',$line[1],$size)){
176                    $data[$type[1].'w'] = $size[1];
177                    $data[$type[1].'h'] = $size[3];
178								}
179						}
180
181						// handle negated options, converts "!crop" to "crop = false"
182						if (!$line[1]){
183								if (preg_match('/^\!.{1,}/', $line[0], $matches))
184									$line[0] = substr($matches[0],1);
185								else
186									$line[1] = true;
187						}
188//           $column = $this->dthlp->_column($line[0]);
189						$data [$line[0]]=$line[1];
190            // if(isset($matches[2])) {// NOM da verificare
191                // $column['comment'] = $matches[2];
192            // }
193            // if($column['multi']) {
194                // if(!isset($data[$column['key']])) {
195                    // // init with empty array
196                    // // Note that multiple occurrences of the field are
197                    // // practically merged
198                    // $data[$column['key']] = array();
199                // }
200                // $vals = explode(',', $line[1]);
201                // foreach($vals as $val) {
202                    // $val = trim($this->dthlp->_cleanData($val, $column['type']));
203                    // if($val == '') continue;
204                    // if(!in_array($val, $data[$column['key']])) {
205                        // $data[$column['key']][] = $val;
206                    // }
207                // }
208            // } else {
209// //                $data[$column['key']] = $this->dthlp->_cleanData($line[1], $column['type']);
210            // }
211//            $properties[$column['key']] = $column;
212        }
213        // return array(
214            // 'data' => $data, 'command' => $command
215        // ); // not utf8_strlen
216
217				// If in link mode, read instructions from linked page
218				if ($cmd == 'link'){
219					$page = $data['pg'];
220					if (page_exists($page)){
221						$instr = p_cached_instructions(wikiFN($page),false,$page);
222					}
223					if (isset($instr)){
224						foreach($instr as $sec){ //NOM forse si può usare array search
225							if ($sec[0] == 'plugin'){
226								if ($sec[1][0] == 'photogallery'){
227									$rdata = $sec[1][1];
228								}
229							}
230							if (isset($rdata)){
231								$data['ns'] = $rdata['ns'];
232								foreach ($rdata as $key => $value){
233									if ((!isset($data[$key])) and (isset($rdata[$key]))){
234										$data[$key] = $value;
235									}
236								}
237								break;
238							}
239						}
240					}
241					else{
242//						unset($data['pg']);
243					}
244				}
245				return $data;
246    }
247
248    /**
249     * Create output or save the data
250     *
251     * @param   $format   string        output format being rendered
252     * @param   $renderer Doku_Renderer the current renderer object
253     * @param   $data     array         data created by handler()
254     * @return  boolean                 rendered correctly?
255     */
256    function render($mode, Doku_Renderer $R, $data){
257        global $ID;
258				global $conf;
259
260        $this->metaAliases = $this->getMetaTagAliases();
261
262				$cmd = $data['command'];
263        if($mode == 'xhtml'){
264
265						if($this->_auth_check($data)){
266								$R->info['cache'] = false; // Disable global render cache
267								$this->_photo_gallery($data, $R); // Start gallery
268						}
269						elseif($cmd == 'show')
270								msg(sprintf($this->getLang('notauthorized'),$data['ns']),-1);
271						return true;
272        }elseif($mode == 'metadata'){ // NOM da rivedere
273            $rel = p_get_metadata($ID,'relation',METADATA_RENDER_USING_CACHE);
274            $img = $rel['firstimage'];
275            if(empty($img)){
276                $files = $this->_findimages($data);
277            }
278            return true;
279        }
280        return false;
281		}
282
283		function _phpThumbCheck(){
284				$fperm = fileperms(PHOTOGALLERY_PGFETCH_FILE);
285				if (($fperm & PHOTOGALLERY_PGFETCH_EXE_PERM) != PHOTOGALLERY_PGFETCH_EXE_PERM){
286						msg($this->getLang('phpthumbexecerror'),-1);
287						if (@chmod(PHOTOGALLERY_PGFETCH_FILE, $fperm | PHOTOGALLERY_PGFETCH_EXE_PERM)){
288								msg($this->getLang('phpthumbexecpermset'),1);
289								return true;
290						}
291						else{
292								msg($this->getLang('phpthumbpermseterror'),-1);
293								return false;
294						}
295				}
296				return true;
297		}
298
299    /**
300     * Does the gallery formatting
301     */
302    function _photo_gallery($data, $R){
303        global $conf;
304        global $lang;
305				global $ID;
306				global $auth;
307				global $USERINFO;
308
309				//dbg($data);
310        $cmd = $data['command'];
311				if (!$data['rss']){
312					if(($cmd == 'show') and (!$this->_media_folder_exists($data['ns']))){
313							$R->doc .= '<div class="nothing">'.sprintf($this->getLang('nsnotexists'),$data['ns']).'</div>';
314							return true;
315					}elseif (($cmd == 'link') and (!page_exists($data['pg']))){
316							$R->doc .= '<div class="nothing">'.sprintf($this->getLang('pgnotexists'),$data['pg']).'</div>';
317							return true;
318					}
319				}
320
321        $files = $this->_findimages($data);
322
323        // anything found?
324        if(!count($files)){
325            $R->doc .= '<div class="nothing">'.$lang['nothingfound'].'</div>';
326            return;
327        }
328
329				// in not exists create in the media folder a zip file containing all the images and link it
330				if (isset($data['zipfile']))
331						if (class_exists('ZipArchive')){
332								$zip = $data['ns'].":".$data['zipfile'];
333								$this->_createzipfile($files, mediaFN($zip));
334								if($this->_zip_auth_check($data)){
335										$data['ziplink'] = $R->internalmedia($zip,$this->getLang('lnk_download'),null,null,null,null,'linkonly',true);
336								}
337						}
338						else
339							msg($this->getLang('zipdisabled'),2);
340
341				// output pg-container
342				$R->doc .= '<div class="pg-container">'.DOKU_LF;
343
344				// output pg-poster and pg-description
345				if ($data['posteralign'] == 'right'){
346					$this->_description($files,$data,$R);
347					$this->_poster($files,$data,$R);
348				}
349				else{
350					$this->_poster($files,$data,$R);
351					$this->_description($files,$data,$R);
352				}
353
354				// Close container
355				$R->doc .= '</div>'.DOKU_LF;
356				return;
357    }
358
359    // function _showData($data, $R) { // NOM da vedere
360
361        // // if(method_exists($R, 'startSectionEdit')) {
362            // // $data['classes'] .= ' ' . $R->startSectionEdit($data['pos'], 'plugin_data');
363
364            // // $class_name = hsc(sectionID($key, $class_names));
365                    // // $ret .= $this->dthlp->_formatData($data['cols'][$key], $val[$i], $R);
366        // // if(method_exists($R, 'finishSectionEdit')) {
367            // // $R->finishSectionEdit($data['len'] + $data['pos']);
368        // // }
369    // }
370
371    /**
372     * Gather all photos matching the given criteria
373     */
374    function _findimages(&$data){
375        global $conf;
376        $files = array();
377
378        // is a media RSS feed ?
379        if($data['rss']){
380            $files = $this->_loadRSS($data['ns']);
381        }else{
382            $dir = utf8_encodeFN(str_replace(':','/',$data['ns']));
383            // all possible images for the given namespace
384						$depth = $data['recursive'] ? 0 : 1;
385						search($files,
386									 $conf['mediadir'],
387									 'search_media',
388									 array('depth'=>$depth),
389									 $dir);
390        }
391
392        // done, yet?
393        $len = count($files);
394        if(!$len) return $files;
395        if($len == 1) return $files;
396        // filter images
397        for($i=0; $i<$len; $i++){
398						if($data['fullsize'] == true)
399							$files[$i]['fullsize'] = true;
400						$fname = $files[$i]['file'];
401						if (preg_match('/\_([a-z]+?)\_\.(jpe?g|gif|png)$/',$fname,$matches)){
402								$modifier = $matches[1];
403								if(($modifier == 'fullsize') || ($data['fullsize'] == 1))		// Show in full size
404										$files[$i]['fullsize'] = true;
405								elseif($modifier == 'poster')			// Is a video poster image, remove from list
406										$files[$i]['isimg'] = false;
407						}
408            if(!$files[$i]['isimg']){
409								if(preg_match('/(.*?)\.(avi|mov|mp4)$/',$fname,$matches)){	// Is a video
410										$files[$i]['isvid'] = true;
411										$poster = getNS($files[$i]['id']).':'.$matches[1].'_poster_.jpg'; // NOM: così i poster possono solo essere jpeg
412										if(in_array($poster,array_column($files,'id'))) // Check if poster exists
413												$files[$i]['poster'] = $poster;
414								}
415								else{
416//		               unset($files[$i]); // this is faster, because RE was done before
417										array_splice($files, $i, 1); // unset will not reindex the array, so putting the poster on first position fails
418										$len--;
419										$i--;
420								}
421            }
422						else{
423								if($data['filter']){
424									if(!preg_match($data['filter'],noNS($files[$i]['id'])))
425											unset($files[$i]); // NOM da verificare unset come sopra se si decide di usare filter
426							}
427            }
428        }
429				if($len<1) return $files;
430
431        // sort?
432        if($data['sort'] == 'random'){
433            shuffle($files);
434        }else{
435            if($data['sort'] == 'date'){
436                usort($files,array($this,'_datesort'));
437            }elseif($data['sort'] == 'mod'){
438                usort($files,array($this,'_modsort'));
439            }elseif($data['sort'] == 'title'){
440                usort($files,array($this,'_titlesort'));
441            }
442            // reverse?
443            if($data['reverse']) $files = array_reverse($files);
444        }
445
446        // offset?
447        if($data['offset']){
448						$offset = $data['offset'];
449						$files = array_slice($files,$offset);
450				} else{
451						$offset = 0;
452				}
453
454				// puts poster element in first array position
455				$i = array_search($data['posterimg'], array_column($files, 'file'));
456				if ($i != $offset){
457					$tmp = $files[$offset];
458					$files[$offset] = $files[$i];
459					$files[$i] = $tmp;
460				}
461
462        // limit?
463        if($data['limit'])
464						$files = array_slice($files,0,$data['limit']);
465
466        return $files;
467    }
468
469    /**
470     * Loads images from a MediaRSS or ATOM feed
471     */
472    function _loadRSS($url){
473        require_once(DOKU_INC.'inc/FeedParser.php');
474        $feed = new FeedParser();
475        $feed->set_feed_url($url);
476        $feed->init();
477        $files = array();
478
479        // base url to use for broken feeds with non-absolute links
480        $main = parse_url($url);
481        $host = $main['scheme'].'://'.
482                $main['host'].
483                (($main['port'])?':'.$main['port']:'');
484        $path = dirname($main['path']).'/';
485
486        foreach($feed->get_items() as $item){
487            if ($enclosure = $item->get_enclosure()){
488                // skip non-image enclosures
489                if($enclosure->get_type()){
490                    if(substr($enclosure->get_type(),0,5) != 'image') continue;
491                }else{
492                    if(!preg_match('/\.(jpe?g|png|gif)(\?|$)/i',
493                       $enclosure->get_link())) continue;
494                }
495
496                // non absolute links
497                $ilink = $enclosure->get_link();
498                if(!preg_match('/^https?:\/\//i',$ilink)){
499                    if($ilink{0} == '/'){
500                        $ilink = $host.$ilink;
501                    }else{
502                        $ilink = $host.$path.$ilink;
503                    }
504                }
505                $link = $item->link;
506                if(!preg_match('/^https?:\/\//i',$link)){
507                    if($link{0} == '/'){
508                        $link = $host.$link;
509                    }else{
510                        $link = $host.$path.$link;
511                    }
512                }
513
514                $files[] = array(
515                    'id'     => $ilink,
516                    'isimg'  => true,
517                    'file'   => basename($ilink),
518                    // decode to avoid later double encoding
519                    'title'  => htmlspecialchars_decode($enclosure->get_title(),ENT_COMPAT),
520                    'desc'   => strip_tags(htmlspecialchars_decode($enclosure->get_description(),ENT_COMPAT)),
521                    'width'  => $enclosure->get_width(),
522                    'height' => $enclosure->get_height(),
523                    'mtime'  => $item->get_date('U'),
524                    'ctime'  => $item->get_date('U'),
525                    'detail' => $link,
526                );
527            }
528        }
529        return $files;
530    }
531
532    /**
533     * usort callback to sort by file lastmodified time
534     */
535    function _modsort($a,$b){
536        if($a['mtime'] < $b['mtime']) return -1;
537        if($a['mtime'] > $b['mtime']) return 1;
538        return strcmp($a['file'],$b['file']);
539    }
540
541    /**
542     * usort callback to sort by EXIF date
543     */
544    function _datesort($a,$b){
545        $da = $this->_meta($a,'cdate');
546        $db = $this->_meta($b,'cdate');
547        if($da < $db) return -1;
548        if($da > $db) return 1;
549        return strcmp($a['file'],$b['file']);
550    }
551
552    /**
553     * usort callback to sort by EXIF title
554     */
555    function _titlesort($a,$b){
556        $ta = $this->_meta($a,'title');
557        $tb = $this->_meta($b,'title');
558        return strcmp($ta,$tb);
559    }
560
561    /**
562     * Does the lightgallery gallery formatting
563     */
564    function _lightgallery($files,$data,$pgid){
565				$ret = '';
566				$ret .= '<ul id="'.$pgid.'" class="pg-show">'.DOKU_LF;
567
568        $page = 0;
569
570        // build gallery
571				$close_pg = false;
572
573				$i = 0;
574				foreach($files as $img){
575						$ret .= $this->_image($img,$data,$i);
576						$i++;
577				}
578
579				// Close containers
580				$ret .= '</ul>'.DOKU_LF;
581				return $ret;
582		}
583
584    /**
585     * Defines how a poster should look like
586     */
587    function _poster($files,$data,$R){
588				$pgid = 'pg-'.substr(md5($data['ns']),4);
589				if ($data['posteralign'] == 'right')
590					$R->doc .= '<div class="pg-poster pg-right">'.DOKU_LF;
591				else
592					$R->doc .= '<div class="pg-poster pg-left">'.DOKU_LF;
593
594				$img = $files[0];
595				$cmd = $data['command'];
596
597        // calculate poster size
598				$w = $data['pw'];
599				$h = $data['ph'];
600
601				$dim = array('w'=>$w,'h'=>$h);
602
603        //prepare link attributes
604        $a = array();
605				if ($cmd == 'show'){
606						$href = '';
607						$a['data-pg-id'] = $pgid;
608						$a['class'] = 'pg-start';
609				}
610				else{
611						$href = wl($data['pg'], 'gallery0#lg=1&amp;slide=0');
612				}
613        $aatt = buildAttributes($a);
614
615        //prepare img attributes
616        $i           = array();
617        $src = ml($img['id'],$dim);
618
619        $i['width']    = $w;
620        $i['height']   = $h;
621//        $i['border']   = 0;
622        $i['alt']  = $this->_meta($img,'title');
623        $iatt = buildAttributes($i);
624//				$src = ml($img['id'],$dim);
625
626				// Generate output
627        $R->doc .= '<a href="'.$href.'" '.$aatt.'>'.DOKU_LF;
628				$R->doc .= '<img src="'.$src.'" '.$iatt.'/>'.DOKU_LF;
629				$R->doc .= '<div class="pg-zoom">';
630				$R->doc .= '<img src="'.PHOTOGALLERY_IMAGES_REL.'zoom.png" alt=""/>';
631				$R->doc .= '</div>'.DOKU_LF;
632        $R->doc .= '</a>'.DOKU_LF;
633
634				if ($cmd == 'show'){
635					$R->doc .= $this->_lightgallery($files,$data,$pgid);
636
637					// Call lightGallery init function
638					$ch = strval(intval($data['th'])+20);
639					$auto = $data['autoplay'] ? 'true' : 'false';
640					$R->doc .= '<script type="text/javascript">/*<![CDATA[*/'.DOKU_LF;
641					$R->doc .= 'jQuery(function(){';
642					$R->doc .= 'InitPgGallery('.$data['tw'].','.$ch.','.$auto.');';
643					$R->doc .= '});'.DOKU_LF;
644					$R->doc .= '/*!]]>*/</script>'.DOKU_LF;
645
646					// Override styles to match thumb size
647					$R->doc .= '<style>.lg-outer.lg-pull-caption-up.lg-thumb-open .lg-sub-html {bottom:'.$ch.'px;}</style>';
648				}
649				$R->doc .= '</div>'.DOKU_LF;
650    }
651
652    /**
653     * Defines how a description  should look like
654     */
655    function _description($files,$data,$R){
656				$imgcnt = 0;
657				$vidcnt = 0;
658				foreach ($files as $file){
659					if ($file['isimg'])
660						$imgcnt++;
661					elseif ($file['isvid'])
662						$vidcnt++;
663				}
664				if ($data['posteralign'] == 'right')
665					$R->doc .= '<div class="pg-description pg-left">'.DOKU_LF;
666				else
667					$R->doc .= '<div class="pg-description pg-right">'.DOKU_LF;
668
669				$R->header($data['title'],2,0);
670				$R->doc .= '<p>'.$data['description'].'</p>';
671				$R->doc .= '<p>';
672				$info = '';
673				$this->_addString($info,$imgcnt,sprintf($this->getLang('imagescnt'),$imgcnt));
674				$this->_addString($info,$vidcnt,sprintf($this->getLang('videoscnt'),$vidcnt),', ');
675				$R->doc .= $info;
676				if (isset($data['ziplink'])){
677						$R->doc .= ' - '.$data['ziplink'];
678				}
679				$R->doc .= '</p>';
680				$R->doc .= '<p style="text-align:left"><i>'.$data['copyright'].'</i></p>'.DOKU_LF;
681				$R->doc .= '</div>'.DOKU_LF;
682		}
683
684		/**
685     * Defines the lightGallery images markup
686     */
687    function _image(&$img,$data,$idx){
688				global $conf;
689				$tpar = array();
690				$ipar = array();
691				$ID = $img['id'];
692				$tw = $data['tw'];
693				$th = $data['th'];
694				// NOM Sistemare le dimensioni dei poster dei video
695				if($img['isvid']){
696						$vsrc = ml($ID);
697						//$vsrc = ml($ID,$tdim);
698						$topt = 'zc=C'; // Crop to given size
699						if($img['poster']){
700								$ID = $img['poster'];
701								$topt .= '!fltr=over|../images/video_frame.png';
702								$img['meta'] = new JpegMeta(mediaFN($ID));
703								$mw = (int) $this->_meta($img,'width');
704								$mh = (int) $this->_meta($img,'height');
705								$iw = $data['iw'];
706								$ih = $data['ih'];
707								//$ipar['src'] = $tpar['src'];
708						} else{
709								$tpar['src'] = 'video_thumb.png';
710								$ipar['src'] = 'video_poster.jpg';
711								// $iw = $data['iw'];
712								// $ih = $data['ih'];
713						}
714				} else{
715						$mw = (int) $this->_meta($img,'width');
716						$mh = (int) $this->_meta($img,'height');
717						// Test for panorama aspect ratio
718						$img_ar = ($mw > $mh ? $mw/$mh : $mh/$mw);
719						if (preg_match('/([0-9]+):([0-9]+)/',$data['panar'],$matches))
720								$max_ar = $matches[1]/$matches[2];
721						$ispan = ($img_ar > $max_ar);
722						if ($ispan){
723								$vpw = $data['panw'];
724								$vph = $data['panh'];
725						} else{
726								$vpw = $data['iw'];
727								$vph = $data['ih'];
728						}
729						if (($mh > $mw) and ($data['vprot'])) // Invert viewport for portrait images
730								list($vpw,$vph) = array($vph,$vpw);
731						if ($ispan){ // Panorama aspect ratio
732								if ($data['phpthumb'] == true){
733										$topt = 'far=1'; // Force aspect ratio
734										if ($mw > $mh){ // Landscape
735												$tw = floor($data['th'] * 0.6 * $img_ar);
736												$cropw = floor(($tw - $data['tw']) / 2);
737												$topt .= "!fltr=crop|$cropw|$cropw";
738												$topt .= '!fltr=over|../images/pano_landscape.png';
739										} else{ // Portrait or square
740												$th = floor($data['tw'] * 0.6 * $img_ar);
741												$croph = floor(($th - $data['th']) / 2);
742												$topt .= "!fltr=crop|0|0|$croph|$croph";
743												$topt .= '!fltr=over|../images/pano_portrate.png';
744										}
745								}
746						} else{  // Normal image
747								$topt = 'zc=C'; // Crop to given size
748						}
749						// Calculates new image sizes fitting into viewport
750						if ($img['fullsize']){  // Override image size for fullsize
751								$iw = $mw;
752								$ih = $mh;
753						} else{
754								if ($data['crop']){
755										$ratio = $this->_fill_ratio($mw,$mh,$vpw,$vph);
756										$iw = floor($mw * $ratio);
757										$ih = floor($mh * $ratio);
758										if ($iw > $vpw)
759												$iw = $vpw;
760										if ($ih > $vph)
761												$ih = $vph;
762										$iopt = 'zc=C'; // Crop to given size
763								} else{
764										$ratio = $this->_fit_ratio($mw,$mh,$vpw,$vph);
765										$iw = floor($mw * $ratio);
766										$ih = floor($mh * $ratio);
767										$iopt = 'iar=1'; // Simple resize
768								}
769						}
770						// Shows HR overlay
771						if ($iw * $ih > 12000000){
772								$topt .= '!fltr=over|../images/image_hr.png';
773						}
774				}
775
776				//prepare image attributes
777				// $ia  = array();
778				// $ia['width'] = $iw;
779				// $ia['height'] = $ih;
780				// $ia['border'] = 0;
781				// $ia['title'] = $this->_caption($img,$data);
782				// $iatt = buildAttributes($ia); //NOM not used yet
783
784				$tpar['w'] = $tw;
785				$tpar['h'] = $th;
786				$ipar['w'] = $iw;
787				$ipar['h'] = $ih;
788				if ($data['rss'])
789						$tpar['media'] = $ID;
790				else
791						$tpar['media'] = idfilter($ID);
792				$ipar['media'] = $tpar['media'];
793				if ($data['phpthumb'] == true){
794						$tpar['opt'] = $topt;
795						$ipar['opt'] = $iopt;
796				}
797				$ipar['tok'] = media_get_token($ID,$iw,$ih);
798				$tpar['tok'] = media_get_token($ID,$tw,$th);
799				$isrc = PHOTOGALLERY_PGFETCH_REL.'?'. buildURLparams($ipar,'&amp;');
800				$tsrc = PHOTOGALLERY_PGFETCH_REL.'?'. buildURLparams($tpar,'&amp;');
801				// prepare attributes
802				$ta = array();
803				$ta['alt'] = $this->_caption($img,$data);
804				$tatt = buildAttributes($ta);
805				// HTML rendering
806  			$ret ='';
807				$video = '';
808				if($img['isvid']){
809						$video .= '<div id="video'.$idx.'" style="display:none;">'.DOKU_LF;
810						$video .= '<video class="lg-video-object lg-html5" controls preload="metadata">';
811						$video .= '<source src="'.$vsrc.'" type="video/mp4">';
812						$video .= 'HTML5 video not supported.';
813						$video .= '</video>'.DOKU_LF;
814						$video .= '</div>'.DOKU_LF;
815						$ret .= '<li data-poster="'.$isrc.'" data-html="#video'.$idx.'">'.DOKU_LF;
816				} else{
817						$ret .= '<li data-src="'.$isrc.'">'.DOKU_LF;
818				}
819				$ret .= '<img src="'.$tsrc.'" '.$tatt.'/>'.DOKU_LF;
820				$ret .= $video;
821        $ret .= '</li>'.DOKU_LF;
822        return $ret;
823    }
824
825    /**
826     * Return the metadata of an item
827     *
828     * Automatically checks if a JPEGMeta object is available or if all data is
829     * supplied in array
830     */
831    function _meta($img,$opt){
832        if($img['meta']){
833            // map JPEGMeta calls to opt names
834
835            switch($opt){
836                case 'title':
837                    return $img['meta']->getField($this->metaAliases['img_title']);
838                case 'desc':
839                    return $img['meta']->getField($this->metaAliases['img_caption']);
840                case 'keywords':
841                    return $img['meta']->getField($this->metaAliases['img_keywords']);
842                case 'cdate':
843                    return $img['meta']->getField($this->metaAliases['img_date']);
844                case 'width':
845                    return $img['meta']->getField($this->metaAliases['img_width']);
846                case 'height':
847                    return $img['meta']->getField($this->metaAliases['img_height']);
848                default:
849                    return '';
850            }
851        }else{
852            // just return the array field
853            return $img[$opt];
854        }
855    }
856
857    /**
858     * Use the existing DokuWiki-configuration to find all aliases for a given image meta-information
859     *
860     * This is based on @see tpl_get_img_meta()
861     *
862     * @return array
863     */
864    protected function getMetaTagAliases() {
865        $config_files = getConfigFiles('mediameta');
866        foreach ($config_files as $config_file) {
867            if(file_exists($config_file)) {
868                include($config_file);
869            }
870        }
871        /** @var array $fields the included array with metadata */
872
873        $tagAliases = array();
874        foreach($fields as $tag){
875            $t = array();
876            if (!empty($tag[0])) {
877                $t = array($tag[0]);
878            }
879            if(is_array($tag[3])) {
880                $t = array_merge($t,$tag[3]);
881            }
882            $tagAliases[$tag[1]] = $t;
883        }
884        return $tagAliases;
885    }
886
887    /**
888     * Calculates the multiplier needed to resize the image to the given
889     * dimensions
890     *
891     */
892    function _fit_ratio($w, $h, $maxw, $maxh){
893				$ratio = 1;
894				if($w > $maxw){
895						$ratio = $maxw/$w;
896						if($h * $ratio > $maxh){
897								$ratio = $maxh/$h;
898						}
899				}
900				elseif($h > $maxh){
901						$ratio = $maxh/$h;
902						if($w * $ratio > $maxw){
903								$ratio = $maxw/$w;
904						}
905				}
906				return $ratio;
907    }
908
909    function _fill_ratio($w, $h, $maxw, $maxh){
910				$ratio = 1;
911				if($w > $maxw){
912						$ratio = $maxw/$w;
913						if($h * $ratio < $maxh){
914								$ratio = $maxh/$h;
915						}
916				}
917				elseif($h > $maxh){
918						$ratio = $maxh/$h;
919						if($w * $ratio < $maxw){
920								$ratio = $maxw/$w;
921						}
922				}
923				return $ratio;
924    }
925
926    /**
927     * Return the caption for the image
928     */
929    function _caption($img,$data){
930				$ret = '';
931				if ($data['showtitle']){
932						$title = $this->_meta($img,'title');
933						if(isset($title)){
934							$ret .= '<h4>'.hsc($title).'</h4>';
935						}
936				}
937				if ($data['showdescription']) {
938                    $desc = $this->_meta($img,'desc');
939                    if(!empty($desc)){
940                        $ret .= '<p>'.nl2br(hsc($desc)).'</p>';
941                    }
942                }
943				if ($data['showkeywords']) {
944						$keywords = $this->_meta($img,'keywords');
945						if(!empty($keywords)){
946								$ret .= '<p>'.hsc($keywords).'</p>';
947						}
948				}
949				if ($data['showinfo']){
950						$ret .= $this->_exif($img);
951				}
952				if ($data['showfname']){
953						$ret .= '<p>'.hsc($img['file']).'</p>';
954				}
955				return $ret;
956		}
957
958    /**
959     * Return the EXIF data for the image
960     */
961		function _exif($img){
962				// Read EXIF data
963				$jpeg = $img['meta'];
964				$ret = '';
965        if($jpeg){
966						$make  = $jpeg->getField(array('Exif.Make','Exif.TIFFMake'));
967						$model = $jpeg->getField(array('Exif.Model','Exif.TIFFModel'));
968						$model = preg_replace('/^'.$make.' /','',$model);
969						$shutter = $jpeg->getShutterSpeed();
970						$fnumber = $jpeg->getField(array('Exif.FNumber'));
971						$iso = $jpeg->getField(array('Exif.ISOSpeedRatings'));
972						$date = $jpeg->getDateField('EarliestTimeStr');
973						$yy = substr($date ,0,4);
974						$mm = substr($date ,5,2);
975						$dd = substr($date ,8,2);
976						$date = $dd.'/'.$mm.'/'.$yy;
977						$ret .= $date;
978						$this->_addString($ret,$make.$model,$make.' '.$model, ' - ');
979						$this->_addString($ret,$shutter,$shutter.'s',', ');
980						$this->_addString($ret,$fnumber,'f/'.$fnumber,', ');
981						$this->_addString($ret,$iso,'ISO '.$iso,', ');
982						$this->_addString($ret,$ret,null,null,'<p>','</p>');
983				}
984				return $ret;
985		}
986
987    /**
988     * Creates a compressed zip file
989     */
990		function _createzipfile($files,$zipfile,$overwrite = false) {
991			//if the zip file already exists and overwrite is false, return false
992			if(file_exists($zipfile) && !$overwrite) return false;
993			if(count($files)) {
994				//create the archive
995				$zip = new ZipArchive();
996				if($zip->open($zipfile,$overwrite ? ZIPARCHIVE::OVERWRITE : ZIPARCHIVE::CREATE) !== true) {
997					return false;
998				}
999				//add the files
1000				foreach($files as $img) {
1001					$file = mediaFN($img['id']);
1002					$zip->addFile($file,basename(dirname($file)).'/'.basename($file));
1003				}
1004
1005				//close the zip -- done!
1006				$zip->close();
1007
1008				//check to make sure the file exists
1009				return file_exists($zipfile);
1010			}
1011			else {
1012				return false;
1013			}
1014		}
1015
1016    /**
1017     * Check ACLs on Gallery
1018     */
1019		function _auth_check($data){
1020				global $USERINFO;
1021				global $auth;
1022				global $conf;
1023
1024				if(!$auth) return false;
1025				$user .= $_SERVER['REMOTE_USER'];
1026
1027				if(is_null($user)) return false;
1028				$groups = (array) $USERINFO['grps'];
1029				$authlist = $data['authlist'];
1030				if (isset($authlist)){
1031					$authlist .= ','.$conf['superuser'];
1032					return auth_isMember($authlist, $user, $groups);
1033				}
1034				else
1035					return true;
1036		}
1037
1038    /**
1039     * Check ACLs on Zip link
1040     */
1041		function _zip_auth_check($data){
1042				global $USERINFO;
1043				global $auth;
1044				global $conf;
1045
1046				if(!$auth) return false;
1047				$user .= $_SERVER['REMOTE_USER'];
1048
1049				if(is_null($user)) return false;
1050				$groups = (array) $USERINFO['grps'];
1051				$authlist = $data['zipauthlist'];
1052				if (isset($authlist)){
1053					$authlist .= ','.$conf['superuser'];
1054					return auth_isMember($authlist, $user, $groups);
1055				}
1056				else
1057					return true;
1058		}
1059
1060    /**
1061     * Return if a namespace has exists as media folder
1062     */
1063		function _media_folder_exists($ns){
1064				global $conf;
1065				return is_dir($conf['mediadir'].'/'.utf8_encodeFN(str_replace(':','/',$ns)));
1066		}
1067
1068    /**
1069     * Sets additional comma separated options
1070     */
1071		function _setConfOptions(&$data, $optstr){
1072				$opts = explode(',', $optstr);
1073				foreach ($opts as $opt)
1074					$data[trim($opt)] = true;
1075		}
1076
1077		/**
1078     * Adds a string to $source only if $check is true.
1079     */
1080		function _addString(&$source, $check, $value = '', $separator = '', $prefix = '', $suffix = ''){
1081				if($check){
1082						if($source)
1083								$source .= $separator;
1084						$source .= $value;
1085						$source = $prefix.$source.$suffix;
1086				}
1087		}
1088}
1089//				$jpeg = new JpegMeta(mediaFN($img['id']));
1090				// if($ext == 'jpg' || $ext == 'jpeg') {
1091                // //try to use the caption from IPTC/EXIF
1092                // require_once(DOKU_INC.'inc/JpegMeta.php');
1093                // $jpeg = new JpegMeta(mediaFN($src));
1094                // if($jpeg !== false) $cap = $jpeg->getTitle();
1095
1096				// $exif_data = exif_read_data($path,'IFD0',0);
1097				// $emake = $exif_data['Make'];
1098				// $emodel = $exif_data['Model'];
1099				// $emodel = str_replace($emake,"",$emodel);
1100				// $eexposuretime = $exif_data['ExposureTime'];
1101		// //							$efnumber = $exif_data['FNumber'];
1102				// $efnumber = $exif_data['COMPUTED']['ApertureFNumber'];
1103				// $eiso = $exif_data['ISOSpeedRatings'];
1104				// $edate = $exif_data['DateTimeOriginal'];
1105				// $yy = substr($edate ,0,4);
1106				// $mm = substr($edate ,5,2);
1107				// $dd = substr($edate ,8,2);
1108				// $h =  substr($edate ,11,2);
1109				// $m =  substr($edate ,14,2);
1110				// $s =  substr($edate ,17,2);
1111				// $date = $dd.'/'.$mm.'/'.$yy;
1112				// $time = $h.':'.$m.':'.$s;
1113				// return $date." - ".$emake." ".$emodel.", ".$eexposuretime."s, ".$efnumber.", ISO ".$eiso;
1114           // $page = $this->_apply_macro($page, $parent_id);
1115            // resolve_pageid(getNS($parent_id), $page, $exists); // resolve shortcuts and clean ID
1116            // if (auth_quickaclcheck($page) >= AUTH_READ)
1117                // $pages[] = $page;
1118
1119	    // function _showname($img,$data){
1120
1121        // //prepare link
1122        // $lnk = ml($img['id'],array('id'=>$ID),false);
1123
1124        // // prepare output
1125        // $ret .= hsc($img['file']);