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