xref: /dokuwiki/inc/media.php (revision ed823bcd0f9414c63e46e8ff593ddc9bb92e281a)
1<?php
2/**
3 * All output and handler function needed for the media management popup
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <andi@splitbrain.org>
7 */
8use dokuwiki\Ui\MediaRevisions;
9use dokuwiki\Cache\CacheImageMod;
10use splitbrain\slika\Exception;
11use dokuwiki\PassHash;
12use dokuwiki\ChangeLog\MediaChangeLog;
13use dokuwiki\Extension\Event;
14use dokuwiki\Form\Form;
15use dokuwiki\HTTP\DokuHTTPClient;
16use dokuwiki\Logger;
17use dokuwiki\Subscriptions\MediaSubscriptionSender;
18use dokuwiki\Ui\Media\DisplayRow;
19use dokuwiki\Ui\Media\DisplayTile;
20use dokuwiki\Ui\MediaDiff;
21use dokuwiki\Utf8\PhpString;
22use dokuwiki\Utf8\Sort;
23use splitbrain\slika\Slika;
24
25/**
26 * Lists pages which currently use a media file selected for deletion
27 *
28 * References uses the same visual as search results and share
29 * their CSS tags except pagenames won't be links.
30 *
31 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
32 *
33 * @param array $data
34 * @param string $id
35 */
36function media_filesinuse($data, $id)
37{
38    global $lang;
39    echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>';
40    echo '<p>'.hsc($lang['ref_inuse']).'</p>';
41
42    $hidden=0; //count of hits without read permission
43    foreach($data as $row){
44        if(auth_quickaclcheck($row) >= AUTH_READ && isVisiblePage($row)){
45            echo '<div class="search_result">';
46            echo '<span class="mediaref_ref">'.hsc($row).'</span>';
47            echo '</div>';
48        }else
49            $hidden++;
50    }
51    if ($hidden){
52        print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>';
53    }
54}
55
56/**
57 * Handles the saving of image meta data
58 *
59 * @author Andreas Gohr <andi@splitbrain.org>
60 * @author Kate Arzamastseva <pshns@ukr.net>
61 *
62 * @param string $id media id
63 * @param int $auth permission level
64 * @param array $data
65 * @return false|string
66 */
67function media_metasave($id, $auth, $data)
68{
69    if($auth < AUTH_UPLOAD) return false;
70    if(!checkSecurityToken()) return false;
71    global $lang;
72    global $conf;
73    $src = mediaFN($id);
74
75    $meta = new JpegMeta($src);
76    $meta->_parseAll();
77
78    foreach($data as $key => $val){
79        $val=trim($val);
80        if(empty($val)){
81            $meta->deleteField($key);
82        }else{
83            $meta->setField($key, $val);
84        }
85    }
86
87    $old = @filemtime($src);
88    if(!file_exists(mediaFN($id, $old)) && file_exists($src)) {
89        // add old revision to the attic
90        media_saveOldRevision($id);
91    }
92    $filesize_old = filesize($src);
93    if($meta->save()){
94        if($conf['fperm']) chmod($src, $conf['fperm']);
95        @clearstatcache(true, $src);
96        $new = @filemtime($src);
97        $filesize_new = filesize($src);
98        $sizechange = $filesize_new - $filesize_old;
99
100        // add a log entry to the media changelog
101        addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange);
102
103        msg($lang['metasaveok'], 1);
104        return $id;
105    }else{
106        msg($lang['metasaveerr'], -1);
107        return false;
108    }
109}
110
111/**
112 * check if a media is external source
113 *
114 * @author Gerrit Uitslag <klapinklapin@gmail.com>
115 *
116 * @param string $id the media ID or URL
117 * @return bool
118 */
119function media_isexternal($id)
120{
121    if (preg_match('#^(?:https?|ftp)://#i', $id)) return true;
122    return false;
123}
124
125/**
126 * Check if a media item is public (eg, external URL or readable by @ALL)
127 *
128 * @author Andreas Gohr <andi@splitbrain.org>
129 *
130 * @param string $id  the media ID or URL
131 * @return bool
132 */
133function media_ispublic($id)
134{
135    if(media_isexternal($id)) return true;
136    $id = cleanID($id);
137    if(auth_aclcheck(getNS($id).':*', '', []) >= AUTH_READ) return true;
138    return false;
139}
140
141/**
142 * Display the form to edit image meta data
143 *
144 * @author Andreas Gohr <andi@splitbrain.org>
145 * @author Kate Arzamastseva <pshns@ukr.net>
146 *
147 * @param string $id media id
148 * @param int $auth permission level
149 * @return bool
150 */
151function media_metaform($id, $auth)
152{
153    global $lang;
154
155    if ($auth < AUTH_UPLOAD) {
156        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.DOKU_LF;
157        return false;
158    }
159
160    // load the field descriptions
161    static $fields = null;
162    if ($fields === null) {
163        $config_files = getConfigFiles('mediameta');
164        foreach ($config_files as $config_file) {
165            if (file_exists($config_file)) include($config_file);
166        }
167    }
168
169    $src = mediaFN($id);
170
171    // output
172    $form = new Form([
173            'action' => media_managerURL(['tab_details' => 'view'], '&'),
174            'class' => 'meta'
175    ]);
176    $form->addTagOpen('div')->addClass('no');
177    $form->setHiddenField('img', $id);
178    $form->setHiddenField('mediado', 'save');
179    foreach ($fields as $key => $field) {
180        // get current value
181        if (empty($field[0])) continue;
182        $tags = [$field[0]];
183        if (is_array($field[3])) $tags = array_merge($tags, $field[3]);
184        $value = tpl_img_getTag($tags, '', $src);
185        $value = cleanText($value);
186
187        // prepare attributes
188        $p = [
189            'class' => 'edit',
190            'id'    => 'meta__'.$key,
191            'name'  => 'meta['.$field[0].']'
192        ];
193
194        $form->addTagOpen('div')->addClass('row');
195        if ($field[2] == 'text') {
196            $form->addTextInput(
197                $p['name'],
198                ($lang[$field[1]] ?: $field[1] . ':')
199            )->id($p['id'])->addClass($p['class'])->val($value);
200        } else {
201            $form->addTextarea($p['name'], $lang[$field[1]])->id($p['id'])
202                ->val(formText($value))
203                ->addClass($p['class'])
204                ->attr('rows', '6')->attr('cols', '50');
205        }
206        $form->addTagClose('div');
207    }
208    $form->addTagOpen('div')->addClass('buttons');
209    $form->addButton('mediado[save]', $lang['btn_save'])->attr('type', 'submit')
210        ->attrs(['accesskey' => 's']);
211    $form->addTagClose('div');
212
213    $form->addTagClose('div');
214    echo $form->toHTML();
215    return true;
216}
217
218/**
219 * Convenience function to check if a media file is still in use
220 *
221 * @author Michael Klier <chi@chimeric.de>
222 *
223 * @param string $id media id
224 * @return array|bool
225 */
226function media_inuse($id)
227{
228    global $conf;
229
230    if($conf['refcheck']){
231        $mediareferences = ft_mediause($id, true);
232        if($mediareferences === []) {
233            return false;
234        } else {
235            return $mediareferences;
236        }
237    } else {
238        return false;
239    }
240}
241
242/**
243 * Handles media file deletions
244 *
245 * If configured, checks for media references before deletion
246 *
247 * @author             Andreas Gohr <andi@splitbrain.org>
248 *
249 * @param string $id media id
250 * @param int $auth no longer used
251 * @return int One of: 0,
252 *                     DOKU_MEDIA_DELETED,
253 *                     DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS,
254 *                     DOKU_MEDIA_NOT_AUTH,
255 *                     DOKU_MEDIA_INUSE
256 */
257function media_delete($id, $auth)
258{
259    global $lang;
260    $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':'));
261    if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH;
262    if(media_inuse($id)) return DOKU_MEDIA_INUSE;
263
264    $file = mediaFN($id);
265
266    // trigger an event - MEDIA_DELETE_FILE
267    $data = [];
268    $data['id']   = $id;
269    $data['name'] = PhpString::basename($file);
270    $data['path'] = $file;
271    $data['size'] = (file_exists($file)) ? filesize($file) : 0;
272
273    $data['unl'] = false;
274    $data['del'] = false;
275    $evt = new Event('MEDIA_DELETE_FILE', $data);
276    if ($evt->advise_before()) {
277        $old = @filemtime($file);
278        if(!file_exists(mediaFN($id, $old)) && file_exists($file)) {
279            // add old revision to the attic
280            media_saveOldRevision($id);
281        }
282
283        $data['unl'] = @unlink($file);
284        if($data['unl']) {
285            $sizechange = 0 - $data['size'];
286            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange);
287
288            $data['del'] = io_sweepNS($id, 'mediadir');
289        }
290    }
291    $evt->advise_after();
292    unset($evt);
293
294    if($data['unl'] && $data['del']){
295        return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS;
296    }
297
298    return $data['unl'] ? DOKU_MEDIA_DELETED : 0;
299}
300
301/**
302 * Handle file uploads via XMLHttpRequest
303 *
304 * @param string $ns   target namespace
305 * @param int    $auth current auth check result
306 * @return false|string false on error, id of the new file on success
307 */
308function media_upload_xhr($ns, $auth)
309{
310    if(!checkSecurityToken()) return false;
311    global $INPUT;
312
313    $id = $INPUT->get->str('qqfile');
314    [$ext, $mime] = mimetype($id);
315    $input = fopen("php://input", "r");
316    if (!($tmp = io_mktmpdir())) return false;
317    $path = $tmp.'/'.md5($id);
318    $target = fopen($path, "w");
319    $realSize = stream_copy_to_stream($input, $target);
320    fclose($target);
321    fclose($input);
322    if ($INPUT->server->has('CONTENT_LENGTH') && ($realSize != $INPUT->server->int('CONTENT_LENGTH'))) {
323        unlink($path);
324        return false;
325    }
326
327    $res = media_save(
328        ['name' => $path, 'mime' => $mime, 'ext'  => $ext],
329        $ns.':'.$id,
330        ($INPUT->get->str('ow') == 'true'),
331        $auth,
332        'copy'
333    );
334    unlink($path);
335    if ($tmp) io_rmdir($tmp, true);
336    if (is_array($res)) {
337        msg($res[0], $res[1]);
338        return false;
339    }
340    return $res;
341}
342
343/**
344 * Handles media file uploads
345 *
346 * @author Andreas Gohr <andi@splitbrain.org>
347 * @author Michael Klier <chi@chimeric.de>
348 *
349 * @param string     $ns    target namespace
350 * @param int        $auth  current auth check result
351 * @param bool|array $file  $_FILES member, $_FILES['upload'] if false
352 * @return false|string false on error, id of the new file on success
353 */
354function media_upload($ns, $auth, $file = false)
355{
356    if(!checkSecurityToken()) return false;
357    global $lang;
358    global $INPUT;
359
360    // get file and id
361    $id   = $INPUT->post->str('mediaid');
362    if (!$file) $file = $_FILES['upload'];
363    if(empty($id)) $id = $file['name'];
364
365    // check for errors (messages are done in lib/exe/mediamanager.php)
366    if($file['error']) return false;
367
368    // check extensions
369    [$fext, $fmime] = mimetype($file['name']);
370    [$iext, $imime] = mimetype($id);
371    if($fext && !$iext){
372        // no extension specified in id - read original one
373        $id   .= '.'.$fext;
374        $imime = $fmime;
375    }elseif($fext && $fext != $iext){
376        // extension was changed, print warning
377        msg(sprintf($lang['mediaextchange'], $fext, $iext));
378    }
379
380    $res = media_save(
381        [
382            'name' => $file['tmp_name'],
383            'mime' => $imime,
384            'ext' => $iext
385        ],
386        $ns . ':' . $id,
387        $INPUT->post->bool('ow'),
388        $auth,
389        'copy_uploaded_file'
390    );
391    if (is_array($res)) {
392        msg($res[0], $res[1]);
393        return false;
394    }
395    return $res;
396}
397
398/**
399 * An alternative to move_uploaded_file that copies
400 *
401 * Using copy, makes sure any setgid bits on the media directory are honored
402 *
403 * @see   move_uploaded_file()
404 *
405 * @param string $from
406 * @param string $to
407 * @return bool
408 */
409function copy_uploaded_file($from, $to)
410{
411    if(!is_uploaded_file($from)) return false;
412    $ok = copy($from, $to);
413    @unlink($from);
414    return $ok;
415}
416
417/**
418 * This generates an action event and delegates to _media_upload_action().
419 * Action plugins are allowed to pre/postprocess the uploaded file.
420 * (The triggered event is preventable.)
421 *
422 * Event data:
423 * $data[0]     fn_tmp:    the temporary file name (read from $_FILES)
424 * $data[1]     fn:        the file name of the uploaded file
425 * $data[2]     id:        the future directory id of the uploaded file
426 * $data[3]     imime:     the mimetype of the uploaded file
427 * $data[4]     overwrite: if an existing file is going to be overwritten
428 * $data[5]     move:      name of function that performs move/copy/..
429 *
430 * @triggers MEDIA_UPLOAD_FINISH
431 *
432 * @param array  $file
433 * @param string $id   media id
434 * @param bool   $ow   overwrite?
435 * @param int    $auth permission level
436 * @param string $move name of functions that performs move/copy/..
437 * @return false|array|string
438 */
439function media_save($file, $id, $ow, $auth, $move)
440{
441    if($auth < AUTH_UPLOAD) {
442        return ["You don't have permissions to upload files.", -1];
443    }
444
445    if (!isset($file['mime']) || !isset($file['ext'])) {
446        [$ext, $mime] = mimetype($id);
447        if (!isset($file['mime'])) {
448            $file['mime'] = $mime;
449        }
450        if (!isset($file['ext'])) {
451            $file['ext'] = $ext;
452        }
453    }
454
455    global $lang, $conf;
456
457    // get filename
458    $id   = cleanID($id);
459    $fn   = mediaFN($id);
460
461    // get filetype regexp
462    $types = array_keys(getMimeTypes());
463    $types = array_map(
464        static fn($q) => preg_quote($q, "/"),
465        $types
466    );
467    $regex = implode('|', $types);
468
469    // because a temp file was created already
470    if(!preg_match('/\.('.$regex.')$/i', $fn)) {
471        return [$lang['uploadwrong'], -1];
472    }
473
474    //check for overwrite
475    $overwrite = file_exists($fn);
476    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
477    if($overwrite && (!$ow || $auth < $auth_ow)) {
478        return [$lang['uploadexist'], 0];
479    }
480    // check for valid content
481    $ok = media_contentcheck($file['name'], $file['mime']);
482    if($ok == -1){
483        return [sprintf($lang['uploadbadcontent'], '.' . $file['ext']), -1];
484    }elseif($ok == -2){
485        return [$lang['uploadspam'], -1];
486    }elseif($ok == -3){
487        return [$lang['uploadxss'], -1];
488    }
489
490    // prepare event data
491    $data = [];
492    $data[0] = $file['name'];
493    $data[1] = $fn;
494    $data[2] = $id;
495    $data[3] = $file['mime'];
496    $data[4] = $overwrite;
497    $data[5] = $move;
498
499    // trigger event
500    return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true);
501}
502
503/**
504 * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH
505 *
506 * @author Michael Klier <chi@chimeric.de>
507 *
508 * @param array $data event data
509 * @return false|array|string
510 */
511function _media_upload_action($data)
512{
513    // fixme do further sanity tests of given data?
514    if(is_array($data) && count($data)===6) {
515        return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
516    } else {
517        return false; //callback error
518    }
519}
520
521/**
522 * Saves an uploaded media file
523 *
524 * @author Andreas Gohr <andi@splitbrain.org>
525 * @author Michael Klier <chi@chimeric.de>
526 * @author Kate Arzamastseva <pshns@ukr.net>
527 *
528 * @param string $fn_tmp
529 * @param string $fn
530 * @param string $id        media id
531 * @param string $imime     mime type
532 * @param bool   $overwrite overwrite existing?
533 * @param string $move      function name
534 * @return array|string
535 */
536function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file')
537{
538    global $conf;
539    global $lang;
540    global $REV;
541
542    $old = @filemtime($fn);
543    if(!file_exists(mediaFN($id, $old)) && file_exists($fn)) {
544        // add old revision to the attic if missing
545        media_saveOldRevision($id);
546    }
547
548    // prepare directory
549    io_createNamespace($id, 'media');
550
551    $filesize_old = file_exists($fn) ? filesize($fn) : 0;
552
553    if($move($fn_tmp, $fn)) {
554        @clearstatcache(true, $fn);
555        $new = @filemtime($fn);
556        // Set the correct permission here.
557        // Always chmod media because they may be saved with different permissions than expected from the php umask.
558        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
559        chmod($fn, $conf['fmode']);
560        msg($lang['uploadsucc'], 1);
561        media_notify($id, $fn, $imime, $old, $new);
562        // add a log entry to the media changelog
563        $filesize_new = filesize($fn);
564        $sizechange = $filesize_new - $filesize_old;
565        if($REV) {
566            addMediaLogEntry(
567                $new,
568                $id,
569                DOKU_CHANGE_TYPE_REVERT,
570                sprintf($lang['restored'], dformat($REV)),
571                $REV,
572                null,
573                $sizechange
574            );
575        } elseif($overwrite) {
576            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
577        } else {
578            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
579        }
580        return $id;
581    }else{
582        return [$lang['uploadfail'], -1];
583    }
584}
585
586/**
587 * Moves the current version of media file to the media_attic
588 * directory
589 *
590 * @author Kate Arzamastseva <pshns@ukr.net>
591 *
592 * @param string $id
593 * @return int - revision date
594 */
595function media_saveOldRevision($id)
596{
597    global $conf, $lang;
598
599    $oldf = mediaFN($id);
600    if (!file_exists($oldf)) return '';
601    $date = filemtime($oldf);
602    if (!$conf['mediarevisions']) return $date;
603
604    $medialog = new MediaChangeLog($id);
605    if (!$medialog->getRevisionInfo($date)) {
606        // there was an external edit,
607        // there is no log entry for current version of file
608        $sizechange = filesize($oldf);
609        if(!file_exists(mediaMetaFN($id, '.changes'))) {
610            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
611        } else {
612            $oldRev = $medialog->getRevisions(-1, 1); // from changelog
613            $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]);
614            $filesize_old = filesize(mediaFN($id, $oldRev));
615            $sizechange -= $filesize_old;
616
617            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
618        }
619    }
620
621    $newf = mediaFN($id, $date);
622    io_makeFileDir($newf);
623    if (copy($oldf, $newf)) {
624        // Set the correct permission here.
625        // Always chmod media because they may be saved with different permissions than expected from the php umask.
626        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
627        chmod($newf, $conf['fmode']);
628    }
629    return $date;
630}
631
632/**
633 * This function checks if the uploaded content is really what the
634 * mimetype says it is. We also do spam checking for text types here.
635 *
636 * We need to do this stuff because we can not rely on the browser
637 * to do this check correctly. Yes, IE is broken as usual.
638 *
639 * @author Andreas Gohr <andi@splitbrain.org>
640 * @link   http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting
641 * @fixme  check all 26 magic IE filetypes here?
642 *
643 * @param string $file path to file
644 * @param string $mime mimetype
645 * @return int
646 */
647function media_contentcheck($file, $mime)
648{
649    global $conf;
650    if($conf['iexssprotect']){
651        $fh = @fopen($file, 'rb');
652        if($fh){
653            $bytes = fread($fh, 256);
654            fclose($fh);
655            if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i', $bytes)){
656                return -3; //XSS: possibly malicious content
657            }
658        }
659    }
660    if(substr($mime, 0, 6) == 'image/'){
661        $info = @getimagesize($file);
662        if($mime == 'image/gif' && $info[2] != 1){
663            return -1; // uploaded content did not match the file extension
664        }elseif($mime == 'image/jpeg' && $info[2] != 2){
665            return -1;
666        }elseif($mime == 'image/png' && $info[2] != 3){
667            return -1;
668        }
669        # fixme maybe check other images types as well
670    }elseif(substr($mime, 0, 5) == 'text/'){
671        global $TEXT;
672        $TEXT = io_readFile($file);
673        if(checkwordblock()){
674            return -2; //blocked by the spam blacklist
675        }
676    }
677    return 0;
678}
679
680/**
681 * Send a notify mail on uploads
682 *
683 * @author Andreas Gohr <andi@splitbrain.org>
684 *
685 * @param string   $id      media id
686 * @param string   $file    path to file
687 * @param string   $mime    mime type
688 * @param bool|int $old_rev revision timestamp or false
689 */
690function media_notify($id, $file, $mime, $old_rev = false, $current_rev = false)
691{
692    global $conf;
693    if(empty($conf['notify'])) return; //notify enabled?
694
695    $subscription = new MediaSubscriptionSender();
696    $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev);
697}
698
699/**
700 * List all files in a given Media namespace
701 *
702 * @param string      $ns             namespace
703 * @param null|int    $auth           permission level
704 * @param string      $jump           id
705 * @param bool        $fullscreenview
706 * @param bool|string $sort           sorting order, false skips sorting
707 */
708function media_filelist($ns, $auth = null, $jump = '', $fullscreenview = false, $sort = false)
709{
710    global $conf;
711    global $lang;
712    $ns = cleanID($ns);
713
714    // check auth our self if not given (needed for ajax calls)
715    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
716
717    if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
718
719    if($auth < AUTH_READ){
720        // FIXME: print permission warning here instead?
721        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
722    }else{
723        if (!$fullscreenview) {
724            media_uploadform($ns, $auth);
725            media_searchform($ns);
726        }
727
728        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
729        $data = [];
730        search($data, $conf['mediadir'], 'search_mediafiles',
731                ['showmsg'=>true, 'depth'=>1], $dir, 1, $sort);
732
733        if(!count($data)){
734            echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
735        }else {
736            if ($fullscreenview) {
737                echo '<ul class="' . _media_get_list_type() . '">';
738            }
739            foreach($data as $item){
740                if (!$fullscreenview) {
741                    //FIXME old call: media_printfile($item,$auth,$jump);
742                    $display = new DisplayRow($item);
743                    $display->scrollIntoView($jump == $item->getID());
744                    $display->show();
745                } else {
746                    //FIXME old call: media_printfile_thumbs($item,$auth,$jump);
747                    echo '<li>';
748                    $display = new DisplayTile($item);
749                    $display->scrollIntoView($jump == $item->getID());
750                    $display->show();
751                    echo '</li>';
752                }
753            }
754            if ($fullscreenview) echo '</ul>'.NL;
755        }
756    }
757}
758
759/**
760 * Prints tabs for files list actions
761 *
762 * @author Kate Arzamastseva <pshns@ukr.net>
763 * @author Adrian Lang <mail@adrianlang.de>
764 *
765 * @param string $selected_tab - opened tab
766 */
767
768function media_tabs_files($selected_tab = '')
769{
770    global $lang;
771    $tabs = [];
772    foreach([
773                'files'  => 'mediaselect',
774                'upload' => 'media_uploadtab',
775                'search' => 'media_searchtab'
776            ] as $tab => $caption) {
777        $tabs[$tab] = [
778            'href'    => media_managerURL(['tab_files' => $tab], '&'),
779            'caption' => $lang[$caption]
780        ];
781    }
782
783    html_tabs($tabs, $selected_tab);
784}
785
786/**
787 * Prints tabs for files details actions
788 *
789 * @author Kate Arzamastseva <pshns@ukr.net>
790 * @param string $image filename of the current image
791 * @param string $selected_tab opened tab
792 */
793function media_tabs_details($image, $selected_tab = '')
794{
795    global $lang, $conf;
796
797    $tabs = [];
798    $tabs['view'] = [
799        'href'    => media_managerURL(['tab_details' => 'view'], '&'),
800        'caption' => $lang['media_viewtab']
801    ];
802
803    [, $mime] = mimetype($image);
804    if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) {
805        $tabs['edit'] = [
806            'href'    => media_managerURL(['tab_details' => 'edit'], '&'),
807            'caption' => $lang['media_edittab']
808        ];
809    }
810    if ($conf['mediarevisions']) {
811        $tabs['history'] = [
812            'href'    => media_managerURL(['tab_details' => 'history'], '&'),
813            'caption' => $lang['media_historytab']
814        ];
815    }
816
817    html_tabs($tabs, $selected_tab);
818}
819
820/**
821 * Prints options for the tab that displays a list of all files
822 *
823 * @author Kate Arzamastseva <pshns@ukr.net>
824 */
825function media_tab_files_options()
826{
827    global $lang;
828    global $INPUT;
829    global $ID;
830
831    $form = new Form([
832            'method' => 'get',
833            'action' => wl($ID),
834            'class' => 'options'
835    ]);
836    $form->addTagOpen('div')->addClass('no');
837    $form->setHiddenField('sectok', null);
838    $media_manager_params = media_managerURL([], '', false, true);
839    foreach ($media_manager_params as $pKey => $pVal) {
840        $form->setHiddenField($pKey, $pVal);
841    }
842    if ($INPUT->has('q')) {
843        $form->setHiddenField('q', $INPUT->str('q'));
844    }
845    $form->addHTML('<ul>'.NL);
846    foreach ([
847                 'list' => ['listType', ['thumbs', 'rows']],
848                 'sort' => ['sortBy', ['name', 'date']]
849             ] as $group => $content) {
850        $checked = "_media_get_{$group}_type";
851        $checked = $checked();
852
853        $form->addHTML('<li class="'. $content[0] .'">');
854        foreach ($content[1] as $option) {
855            $attrs = [];
856            if ($checked == $option) {
857                $attrs['checked'] = 'checked';
858            }
859            $radio = $form->addRadioButton(
860                $group.'_dwmedia',
861                $lang['media_'.$group.'_'.$option]
862            )->val($option)->id($content[0].'__'.$option)->addClass($option);
863            $radio->attrs($attrs);
864        }
865        $form->addHTML('</li>'.NL);
866    }
867    $form->addHTML('<li>');
868    $form->addButton('', $lang['btn_apply'])->attr('type', 'submit');
869    $form->addHTML('</li>'.NL);
870    $form->addHTML('</ul>'.NL);
871    $form->addTagClose('div');
872    print $form->toHTML();
873}
874
875/**
876 * Returns type of sorting for the list of files in media manager
877 *
878 * @author Kate Arzamastseva <pshns@ukr.net>
879 *
880 * @return string - sort type
881 */
882function _media_get_sort_type()
883{
884    return _media_get_display_param('sort', ['default' => 'name', 'date']);
885}
886
887/**
888 * Returns type of listing for the list of files in media manager
889 *
890 * @author Kate Arzamastseva <pshns@ukr.net>
891 *
892 * @return string - list type
893 */
894function _media_get_list_type()
895{
896    return _media_get_display_param('list', ['default' => 'thumbs', 'rows']);
897}
898
899/**
900 * Get display parameters
901 *
902 * @param string $param   name of parameter
903 * @param array  $values  allowed values, where default value has index key 'default'
904 * @return string the parameter value
905 */
906function _media_get_display_param($param, $values)
907{
908    global $INPUT;
909    if (in_array($INPUT->str($param), $values)) {
910        // FIXME: Set cookie
911        return $INPUT->str($param);
912    } else {
913        $val = get_doku_pref($param, $values['default']);
914        if (!in_array($val, $values)) {
915            $val = $values['default'];
916        }
917        return $val;
918    }
919}
920
921/**
922 * Prints tab that displays a list of all files
923 *
924 * @author Kate Arzamastseva <pshns@ukr.net>
925 *
926 * @param string    $ns
927 * @param null|int  $auth permission level
928 * @param string    $jump item id
929 */
930function media_tab_files($ns, $auth = null, $jump = '')
931{
932    global $lang;
933    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
934
935    if($auth < AUTH_READ){
936        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
937    }else{
938        media_filelist($ns, $auth, $jump, true, _media_get_sort_type());
939    }
940}
941
942/**
943 * Prints tab that displays uploading form
944 *
945 * @author Kate Arzamastseva <pshns@ukr.net>
946 *
947 * @param string   $ns
948 * @param null|int $auth permission level
949 * @param string   $jump item id
950 */
951function media_tab_upload($ns, $auth = null, $jump = '')
952{
953    global $lang;
954    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
955
956    echo '<div class="upload">'.NL;
957    if ($auth >= AUTH_UPLOAD) {
958        echo '<p>' . $lang['mediaupload'] . '</p>';
959    }
960    media_uploadform($ns, $auth, true);
961    echo '</div>'.NL;
962}
963
964/**
965 * Prints tab that displays search form
966 *
967 * @author Kate Arzamastseva <pshns@ukr.net>
968 *
969 * @param string $ns
970 * @param null|int $auth permission level
971 */
972function media_tab_search($ns, $auth = null)
973{
974    global $INPUT;
975
976    $do = $INPUT->str('mediado');
977    $query = $INPUT->str('q');
978    echo '<div class="search">'.NL;
979
980    media_searchform($ns, $query, true);
981    if ($do == 'searchlist' || $query) {
982        media_searchlist($query, $ns, $auth, true, _media_get_sort_type());
983    }
984    echo '</div>'.NL;
985}
986
987/**
988 * Prints tab that displays mediafile details
989 *
990 * @author Kate Arzamastseva <pshns@ukr.net>
991 *
992 * @param string     $image media id
993 * @param string     $ns
994 * @param null|int   $auth  permission level
995 * @param string|int $rev   revision timestamp or empty string
996 */
997function media_tab_view($image, $ns, $auth = null, $rev = '')
998{
999    global $lang;
1000    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1001
1002    if ($image && $auth >= AUTH_READ) {
1003        $meta = new JpegMeta(mediaFN($image, $rev));
1004        media_preview($image, $auth, $rev, $meta);
1005        media_preview_buttons($image, $auth, $rev);
1006        media_details($image, $auth, $rev, $meta);
1007
1008    } else {
1009        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1010    }
1011}
1012
1013/**
1014 * Prints tab that displays form for editing mediafile metadata
1015 *
1016 * @author Kate Arzamastseva <pshns@ukr.net>
1017 *
1018 * @param string     $image media id
1019 * @param string     $ns
1020 * @param null|int   $auth permission level
1021 */
1022function media_tab_edit($image, $ns, $auth = null)
1023{
1024    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1025
1026    if ($image) {
1027        [, $mime] = mimetype($image);
1028        if ($mime == 'image/jpeg') media_metaform($image, $auth);
1029    }
1030}
1031
1032/**
1033 * Prints tab that displays mediafile revisions
1034 *
1035 * @author Kate Arzamastseva <pshns@ukr.net>
1036 *
1037 * @param string     $image media id
1038 * @param string     $ns
1039 * @param null|int   $auth permission level
1040 */
1041function media_tab_history($image, $ns, $auth = null)
1042{
1043    global $lang;
1044    global $INPUT;
1045
1046    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1047    $do = $INPUT->str('mediado');
1048
1049    if ($auth >= AUTH_READ && $image) {
1050        if ($do == 'diff'){
1051            (new MediaDiff($image))->show(); //media_diff($image, $ns, $auth);
1052        } else {
1053            $first = $INPUT->int('first', -1);
1054            (new MediaRevisions($image))->show($first);
1055        }
1056    } else {
1057        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1058    }
1059}
1060
1061/**
1062 * Prints mediafile details
1063 *
1064 * @param string         $image media id
1065 * @param int            $auth permission level
1066 * @param int|string     $rev revision timestamp or empty string
1067 * @param JpegMeta|bool  $meta
1068 *
1069 * @author Kate Arzamastseva <pshns@ukr.net>
1070 */
1071function media_preview($image, $auth, $rev = '', $meta = false)
1072{
1073
1074    $size = media_image_preview_size($image, $rev, $meta);
1075
1076    if ($size) {
1077        global $lang;
1078        echo '<div class="image">';
1079
1080        $more = [];
1081        if ($rev) {
1082            $more['rev'] = $rev;
1083        } else {
1084            $t = @filemtime(mediaFN($image));
1085            $more['t'] = $t;
1086        }
1087
1088        $more['w'] = $size[0];
1089        $more['h'] = $size[1];
1090        $src = ml($image, $more);
1091
1092        echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">';
1093        echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
1094        echo '</a>';
1095
1096        echo '</div>';
1097    }
1098}
1099
1100/**
1101 * Prints mediafile action buttons
1102 *
1103 * @author Kate Arzamastseva <pshns@ukr.net>
1104 *
1105 * @param string     $image media id
1106 * @param int        $auth  permission level
1107 * @param int|string $rev   revision timestamp, or empty string
1108 */
1109function media_preview_buttons($image, $auth, $rev = '')
1110{
1111    global $lang, $conf;
1112
1113    echo '<ul class="actions">';
1114
1115    if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) {
1116
1117        // delete button
1118        $form = new Form([
1119            'id' => 'mediamanager__btn_delete',
1120            'action' => media_managerURL(['delete' => $image], '&'),
1121        ]);
1122        $form->addTagOpen('div')->addClass('no');
1123        $form->addButton('', $lang['btn_delete'])->attr('type', 'submit');
1124        $form->addTagClose('div');
1125        echo '<li>';
1126        echo $form->toHTML();
1127        echo '</li>';
1128    }
1129
1130    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1131    if ($auth >= $auth_ow && !$rev) {
1132
1133        // upload new version button
1134        $form = new Form([
1135            'id' => 'mediamanager__btn_update',
1136            'action' => media_managerURL(['image' => $image, 'mediado' => 'update'], '&'),
1137        ]);
1138        $form->addTagOpen('div')->addClass('no');
1139        $form->addButton('', $lang['media_update'])->attr('type', 'submit');
1140        $form->addTagClose('div');
1141        echo '<li>';
1142        echo $form->toHTML();
1143        echo '</li>';
1144    }
1145
1146    if ($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))) {
1147
1148        // restore button
1149        $form = new Form([
1150            'id' => 'mediamanager__btn_restore',
1151            'action'=>media_managerURL(['image' => $image], '&'),
1152        ]);
1153        $form->addTagOpen('div')->addClass('no');
1154        $form->setHiddenField('mediado', 'restore');
1155        $form->setHiddenField('rev', $rev);
1156        $form->addButton('', $lang['media_restore'])->attr('type', 'submit');
1157        $form->addTagClose('div');
1158        echo '<li>';
1159        echo $form->toHTML();
1160        echo '</li>';
1161    }
1162
1163    echo '</ul>';
1164}
1165
1166/**
1167 * Returns image width and height for mediamanager preview panel
1168 *
1169 * @author Kate Arzamastseva <pshns@ukr.net>
1170 * @param string         $image
1171 * @param int|string     $rev
1172 * @param JpegMeta|bool  $meta
1173 * @param int            $size
1174 * @return array
1175 */
1176function media_image_preview_size($image, $rev, $meta = false, $size = 500)
1177{
1178    if (!preg_match("/\.(jpe?g|gif|png)$/", $image)
1179      || !file_exists($filename = mediaFN($image, $rev))
1180    ) return [];
1181
1182    $info = getimagesize($filename);
1183    $w = $info[0];
1184    $h = $info[1];
1185
1186    if ($meta && ($w > $size || $h > $size)) {
1187        $ratio = $meta->getResizeRatio($size, $size);
1188        $w = floor($w * $ratio);
1189        $h = floor($h * $ratio);
1190    }
1191    return [$w, $h];
1192}
1193
1194/**
1195 * Returns the requested EXIF/IPTC tag from the image meta
1196 *
1197 * @author Kate Arzamastseva <pshns@ukr.net>
1198 *
1199 * @param array    $tags array with tags, first existing is returned
1200 * @param JpegMeta $meta
1201 * @param string   $alt  alternative value
1202 * @return string
1203 */
1204function media_getTag($tags, $meta = false, $alt = '')
1205{
1206    if (!$meta) return $alt;
1207    $info = $meta->getField($tags);
1208    if (!$info) return $alt;
1209    return $info;
1210}
1211
1212/**
1213 * Returns mediafile tags
1214 *
1215 * @author Kate Arzamastseva <pshns@ukr.net>
1216 *
1217 * @param JpegMeta $meta
1218 * @return array list of tags of the mediafile
1219 */
1220function media_file_tags($meta)
1221{
1222    // load the field descriptions
1223    static $fields = null;
1224    if (is_null($fields)) {
1225        $config_files = getConfigFiles('mediameta');
1226        foreach ($config_files as $config_file) {
1227            if (file_exists($config_file)) include($config_file);
1228        }
1229    }
1230
1231    $tags = [];
1232
1233    foreach ($fields as $tag) {
1234        $t = [];
1235        if (!empty($tag[0])) $t = [$tag[0]];
1236        if (isset($tag[3]) && is_array($tag[3])) $t = array_merge($t, $tag[3]);
1237        $value = media_getTag($t, $meta);
1238        $tags[] = ['tag' => $tag, 'value' => $value];
1239    }
1240
1241    return $tags;
1242}
1243
1244/**
1245 * Prints mediafile tags
1246 *
1247 * @author Kate Arzamastseva <pshns@ukr.net>
1248 *
1249 * @param string        $image image id
1250 * @param int           $auth  permission level
1251 * @param string|int    $rev   revision timestamp, or empty string
1252 * @param bool|JpegMeta $meta  image object, or create one if false
1253 */
1254function media_details($image, $auth, $rev = '', $meta = false)
1255{
1256    global $lang;
1257
1258    if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
1259    $tags = media_file_tags($meta);
1260
1261    echo '<dl>'.NL;
1262    foreach($tags as $tag){
1263        if ($tag['value']) {
1264            $value = cleanText($tag['value']);
1265            echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>';
1266            if ($tag['tag'][2] == 'date') echo dformat($value);
1267            else echo hsc($value);
1268            echo '</dd>'.NL;
1269        }
1270    }
1271    echo '</dl>'.NL;
1272    echo '<dl>'.NL;
1273    echo '<dt>'.$lang['reference'].':</dt>';
1274    $media_usage = ft_mediause($image, true);
1275    if($media_usage !== []){
1276        foreach($media_usage as $path){
1277            echo '<dd>'.html_wikilink($path).'</dd>';
1278        }
1279    }else{
1280        echo '<dd>'.$lang['nothingfound'].'</dd>';
1281    }
1282    echo '</dl>'.NL;
1283
1284}
1285
1286/**
1287 * Shows difference between two revisions of file
1288 *
1289 * @author Kate Arzamastseva <pshns@ukr.net>
1290 *
1291 * @param string $image  image id
1292 * @param string $ns
1293 * @param int $auth permission level
1294 * @param bool $fromajax
1295 *
1296 * @deprecated 2020-12-31
1297 */
1298function media_diff($image, $ns, $auth, $fromajax = false)
1299{
1300    dbg_deprecated('see '. MediaDiff::class .'::show()');
1301}
1302
1303/**
1304 * Callback for media file diff
1305 *
1306 * @param array $data event data
1307 *
1308 * @deprecated 2020-12-31
1309 */
1310function _media_file_diff($data)
1311{
1312    dbg_deprecated('see '. MediaDiff::class .'::show()');
1313}
1314
1315/**
1316 * Shows difference between two revisions of image
1317 *
1318 * @author Kate Arzamastseva <pshns@ukr.net>
1319 *
1320 * @param string $image
1321 * @param string|int $l_rev revision timestamp, or empty string
1322 * @param string|int $r_rev revision timestamp, or empty string
1323 * @param string $ns
1324 * @param int $auth permission level
1325 * @param bool $fromajax
1326 * @deprecated 2020-12-31
1327 */
1328function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax)
1329{
1330    dbg_deprecated('see '. MediaDiff::class .'::showFileDiff()');
1331}
1332
1333/**
1334 * Prints two images side by side
1335 * and slider
1336 *
1337 * @author Kate Arzamastseva <pshns@ukr.net>
1338 *
1339 * @param string $image   image id
1340 * @param int    $l_rev   revision timestamp, or empty string
1341 * @param int    $r_rev   revision timestamp, or empty string
1342 * @param array  $l_size  array with width and height
1343 * @param array  $r_size  array with width and height
1344 * @param string $type
1345 * @deprecated 2020-12-31
1346 */
1347function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type)
1348{
1349    dbg_deprecated('see '. MediaDiff::class .'::showImageDiff()');
1350}
1351
1352/**
1353 * Restores an old revision of a media file
1354 *
1355 * @param string $image media id
1356 * @param int    $rev   revision timestamp or empty string
1357 * @param int    $auth
1358 * @return string - file's id
1359 *
1360 * @author Kate Arzamastseva <pshns@ukr.net>
1361 */
1362function media_restore($image, $rev, $auth)
1363{
1364    global $conf;
1365    if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
1366    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
1367    if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
1368    if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
1369    [, $imime, ] = mimetype($image);
1370    $res = media_upload_finish(mediaFN($image, $rev),
1371        mediaFN($image),
1372        $image,
1373        $imime,
1374        true,
1375        'copy');
1376    if (is_array($res)) {
1377        msg($res[0], $res[1]);
1378        return false;
1379    }
1380    return $res;
1381}
1382
1383/**
1384 * List all files found by the search request
1385 *
1386 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1387 * @author Andreas Gohr <gohr@cosmocode.de>
1388 * @author Kate Arzamastseva <pshns@ukr.net>
1389 * @triggers MEDIA_SEARCH
1390 *
1391 * @param string $query
1392 * @param string $ns
1393 * @param null|int $auth
1394 * @param bool $fullscreen
1395 * @param string $sort
1396 */
1397function media_searchlist($query, $ns, $auth = null, $fullscreen = false, $sort = 'natural')
1398{
1399    global $conf;
1400    global $lang;
1401
1402    $ns = cleanID($ns);
1403    $evdata = [
1404        'ns'    => $ns,
1405        'data'  => [],
1406        'query' => $query
1407    ];
1408    if (!blank($query)) {
1409        $evt = new Event('MEDIA_SEARCH', $evdata);
1410        if ($evt->advise_before()) {
1411            $dir = utf8_encodeFN(str_replace(':', '/', $evdata['ns']));
1412            $quoted = preg_quote($evdata['query'], '/');
1413            //apply globbing
1414            $quoted = str_replace(['\*', '\?'], ['.*', '.'], $quoted, $count);
1415
1416            //if we use globbing file name must match entirely but may be preceded by arbitrary namespace
1417            if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$';
1418
1419            $pattern = '/'.$quoted.'/i';
1420            search($evdata['data'],
1421                    $conf['mediadir'],
1422                    'search_mediafiles',
1423                    ['showmsg'=>false, 'pattern'=>$pattern],
1424                    $dir,
1425                    1,
1426                    $sort);
1427        }
1428        $evt->advise_after();
1429        unset($evt);
1430    }
1431
1432    if (!$fullscreen) {
1433        echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'], hsc($ns).':*').'</h1>'.NL;
1434        media_searchform($ns, $query);
1435    }
1436
1437    if(!count($evdata['data'])){
1438        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
1439    }else {
1440        if ($fullscreen) {
1441            echo '<ul class="' . _media_get_list_type() . '">';
1442        }
1443        foreach($evdata['data'] as $item){
1444            if (!$fullscreen) {
1445                // FIXME old call: media_printfile($item,$item['perm'],'',true);
1446                $display = new DisplayRow($item);
1447                $display->relativeDisplay($ns);
1448                $display->show();
1449            } else {
1450                // FIXME old call: media_printfile_thumbs($item,$item['perm'],false,true);
1451                $display = new DisplayTile($item);
1452                $display->relativeDisplay($ns);
1453                echo '<li>';
1454                $display->show();
1455                echo '</li>';
1456            }
1457        }
1458        if ($fullscreen) echo '</ul>'.NL;
1459    }
1460}
1461
1462/**
1463 * Display a media icon
1464 *
1465 * @param string $filename media id
1466 * @param string $size     the size subfolder, if not specified 16x16 is used
1467 * @return string html
1468 */
1469function media_printicon($filename, $size = '')
1470{
1471    [$ext] = mimetype(mediaFN($filename), false);
1472
1473    if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) {
1474        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png';
1475    } else {
1476        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png';
1477    }
1478
1479    return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
1480}
1481
1482/**
1483 * Build link based on the current, adding/rewriting parameters
1484 *
1485 * @author Kate Arzamastseva <pshns@ukr.net>
1486 *
1487 * @param array|bool $params
1488 * @param string     $amp           separator
1489 * @param bool       $abs           absolute url?
1490 * @param bool       $params_array  return the parmeters array?
1491 * @return string|array - link or link parameters
1492 */
1493function media_managerURL($params = false, $amp = '&amp;', $abs = false, $params_array = false)
1494{
1495    global $ID;
1496    global $INPUT;
1497
1498    $gets = ['do' => 'media'];
1499    $media_manager_params = ['tab_files', 'tab_details', 'image', 'ns', 'list', 'sort'];
1500    foreach ($media_manager_params as $x) {
1501        if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x);
1502    }
1503
1504    if ($params) {
1505        $gets = $params + $gets;
1506    }
1507    unset($gets['id']);
1508    if (isset($gets['delete'])) {
1509        unset($gets['image']);
1510        unset($gets['tab_details']);
1511    }
1512
1513    if ($params_array) return $gets;
1514
1515    return wl($ID, $gets, $abs, $amp);
1516}
1517
1518/**
1519 * Print the media upload form if permissions are correct
1520 *
1521 * @author Andreas Gohr <andi@splitbrain.org>
1522 * @author Kate Arzamastseva <pshns@ukr.net>
1523 *
1524 * @param string $ns
1525 * @param int    $auth permission level
1526 * @param bool  $fullscreen
1527 */
1528function media_uploadform($ns, $auth, $fullscreen = false)
1529{
1530    global $lang;
1531    global $conf;
1532    global $INPUT;
1533
1534    if ($auth < AUTH_UPLOAD) {
1535        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
1536        return;
1537    }
1538    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1539
1540    $update = false;
1541    $id = '';
1542    if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') {
1543        $update = true;
1544        $id = cleanID($INPUT->str('image'));
1545    }
1546
1547    // The default HTML upload form
1548    $form = new Form([
1549        'id' => 'dw__upload',
1550        'enctype' => 'multipart/form-data',
1551        'action' => ($fullscreen)
1552                    ? media_managerURL(['tab_files' => 'files', 'tab_details' => 'view'], '&')
1553                    : DOKU_BASE.'lib/exe/mediamanager.php',
1554    ]);
1555    $form->addTagOpen('div')->addClass('no');
1556    $form->setHiddenField('ns', hsc($ns));  // FIXME hsc required?
1557    $form->addTagOpen('p');
1558    $form->addTextInput('upload', $lang['txt_upload'])->id('upload__file')
1559            ->attrs(['type' => 'file']);
1560    $form->addTagClose('p');
1561    $form->addTagOpen('p');
1562    $form->addTextInput('mediaid', $lang['txt_filename'])->id('upload__name')
1563            ->val(noNS($id));
1564    $form->addButton('', $lang['btn_upload'])->attr('type', 'submit');
1565    $form->addTagClose('p');
1566    if ($auth >= $auth_ow){
1567        $form->addTagOpen('p');
1568        $attrs = [];
1569        if ($update) $attrs['checked'] = 'checked';
1570        $form->addCheckbox('ow', $lang['txt_overwrt'])->id('dw__ow')->val('1')
1571            ->addClass('check')->attrs($attrs);
1572        $form->addTagClose('p');
1573    }
1574    $form->addTagClose('div');
1575
1576    if (!$fullscreen) {
1577        echo '<div class="upload">'. $lang['mediaupload'] .'</div>'.DOKU_LF;
1578    } else {
1579        echo DOKU_LF;
1580    }
1581
1582    echo '<div id="mediamanager__uploader">'.DOKU_LF;
1583    echo $form->toHTML('Upload');
1584    echo '</div>'.DOKU_LF;
1585
1586    echo '<p class="maxsize">';
1587    printf($lang['maxuploadsize'], filesize_h(media_getuploadsize()));
1588    echo ' <a class="allowedmime" href="#">'. $lang['allowedmime'] .'</a>';
1589    echo ' <span>'. implode(', ', array_keys(getMimeTypes())) .'</span>';
1590    echo '</p>'.DOKU_LF;
1591}
1592
1593/**
1594 * Returns the size uploaded files may have
1595 *
1596 * This uses a conservative approach using the lowest number found
1597 * in any of the limiting ini settings
1598 *
1599 * @returns int size in bytes
1600 */
1601function media_getuploadsize()
1602{
1603    $okay = 0;
1604
1605    $post = php_to_byte(@ini_get('post_max_size'));
1606    $suho = php_to_byte(@ini_get('suhosin.post.max_value_length'));
1607    $upld = php_to_byte(@ini_get('upload_max_filesize'));
1608
1609    if($post && ($post < $okay || $okay === 0)) $okay = $post;
1610    if($suho && ($suho < $okay || $okay == 0)) $okay = $suho;
1611    if($upld && ($upld < $okay || $okay == 0)) $okay = $upld;
1612
1613    return $okay;
1614}
1615
1616/**
1617 * Print the search field form
1618 *
1619 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1620 * @author Kate Arzamastseva <pshns@ukr.net>
1621 *
1622 * @param string $ns
1623 * @param string $query
1624 * @param bool $fullscreen
1625 */
1626function media_searchform($ns, $query = '', $fullscreen = false)
1627{
1628    global $lang;
1629
1630    // The default HTML search form
1631    $form = new Form([
1632        'id'     => 'dw__mediasearch',
1633        'action' => ($fullscreen)
1634                    ? media_managerURL([], '&')
1635                    : DOKU_BASE.'lib/exe/mediamanager.php',
1636    ]);
1637    $form->addTagOpen('div')->addClass('no');
1638    $form->setHiddenField('ns', $ns);
1639    $form->setHiddenField($fullscreen ? 'mediado' : 'do', 'searchlist');
1640
1641    $form->addTagOpen('p');
1642    $form->addTextInput('q', $lang['searchmedia'])
1643            ->attr('title', sprintf($lang['searchmedia_in'], hsc($ns) .':*'))
1644            ->val($query);
1645    $form->addHTML(' ');
1646    $form->addButton('', $lang['btn_search'])->attr('type', 'submit');
1647    $form->addTagClose('p');
1648    $form->addTagClose('div');
1649    print $form->toHTML('SearchMedia');
1650}
1651
1652/**
1653 * Build a tree outline of available media namespaces
1654 *
1655 * @author Andreas Gohr <andi@splitbrain.org>
1656 *
1657 * @param string $ns
1658 */
1659function media_nstree($ns)
1660{
1661    global $conf;
1662    global $lang;
1663
1664    // currently selected namespace
1665    $ns  = cleanID($ns);
1666    if(empty($ns)){
1667        global $ID;
1668        $ns = (string)getNS($ID);
1669    }
1670
1671    $ns_dir  = utf8_encodeFN(str_replace(':', '/', $ns));
1672
1673    $data = [];
1674    search($data, $conf['mediadir'], 'search_index', ['ns' => $ns_dir, 'nofiles' => true]);
1675
1676    // wrap a list with the root level around the other namespaces
1677    array_unshift($data, ['level' => 0, 'id' => '', 'open' =>'true', 'label' => '['.$lang['mediaroot'].']']);
1678
1679    // insert the current ns into the hierarchy if it isn't already part of it
1680    $ns_parts = explode(':', $ns);
1681    $tmp_ns = '';
1682    $pos = 0;
1683    foreach ($ns_parts as $level => $part) {
1684        if ($tmp_ns) $tmp_ns .= ':'.$part;
1685        else $tmp_ns = $part;
1686
1687        // find the namespace parts or insert them
1688        while ($data[$pos]['id'] != $tmp_ns) {
1689            if (
1690                $pos >= count($data) ||
1691                ($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
1692            ) {
1693                array_splice($data, $pos, 0, [['level' => $level+1, 'id' => $tmp_ns, 'open' => 'true']]);
1694                break;
1695            }
1696            ++$pos;
1697        }
1698    }
1699
1700    echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li');
1701}
1702
1703/**
1704 * Userfunction for html_buildlist
1705 *
1706 * Prints a media namespace tree item
1707 *
1708 * @author Andreas Gohr <andi@splitbrain.org>
1709 *
1710 * @param array $item
1711 * @return string html
1712 */
1713function media_nstree_item($item)
1714{
1715    global $INPUT;
1716    $pos   = strrpos($item['id'], ':');
1717    $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0);
1718    if(empty($item['label'])) $item['label'] = $label;
1719
1720    $ret  = '';
1721    if ($INPUT->str('do') != 'media')
1722    $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
1723    else $ret .= '<a href="'.media_managerURL(['ns' => idfilter($item['id'], false), 'tab_files' => 'files'])
1724        .'" class="idx_dir">';
1725    $ret .= $item['label'];
1726    $ret .= '</a>';
1727    return $ret;
1728}
1729
1730/**
1731 * Userfunction for html_buildlist
1732 *
1733 * Prints a media namespace tree item opener
1734 *
1735 * @author Andreas Gohr <andi@splitbrain.org>
1736 *
1737 * @param array $item
1738 * @return string html
1739 */
1740function media_nstree_li($item)
1741{
1742    $class='media level'.$item['level'];
1743    if($item['open']){
1744        $class .= ' open';
1745        $img   = DOKU_BASE.'lib/images/minus.gif';
1746        $alt   = '−';
1747    }else{
1748        $class .= ' closed';
1749        $img   = DOKU_BASE.'lib/images/plus.gif';
1750        $alt   = '+';
1751    }
1752    // TODO: only deliver an image if it actually has a subtree...
1753    return '<li class="'.$class.'">'.
1754        '<img src="'.$img.'" alt="'.$alt.'" />';
1755}
1756
1757/**
1758 * Resizes or crop the given image to the given size
1759 *
1760 * @author  Andreas Gohr <andi@splitbrain.org>
1761 *
1762 * @param string $file filename, path to file
1763 * @param string $ext  extension
1764 * @param int    $w    desired width
1765 * @param int    $h    desired height
1766 * @param bool   $crop should a center crop be used?
1767 * @return string path to resized or original size if failed
1768 */
1769function media_mod_image($file, $ext, $w, $h = 0, $crop = false)
1770{
1771    global $conf;
1772    if(!$h) $h = 0;
1773    // we wont scale up to infinity
1774    if($w > 2000 || $h > 2000) return $file;
1775
1776    $operation = $crop ? 'crop' : 'resize';
1777
1778    $options = [
1779        'quality' => $conf['jpg_quality'],
1780        'imconvert' => $conf['im_convert'],
1781    ];
1782
1783    $cache = new CacheImageMod($file, $w, $h, $ext, $crop);
1784    if(!$cache->useCache()) {
1785        try {
1786            Slika::run($file, $options)
1787                 ->autorotate()
1788                 ->$operation($w, $h)
1789                 ->save($cache->cache, $ext);
1790            if($conf['fperm']) @chmod($cache->cache, $conf['fperm']);
1791        } catch (Exception $e) {
1792            Logger::debug($e->getMessage());
1793            return $file;
1794        }
1795    }
1796
1797    return $cache->cache;
1798}
1799
1800/**
1801 * Resizes the given image to the given size
1802 *
1803 * @author  Andreas Gohr <andi@splitbrain.org>
1804 *
1805 * @param string $file filename, path to file
1806 * @param string $ext  extension
1807 * @param int    $w    desired width
1808 * @param int    $h    desired height
1809 * @return string path to resized or original size if failed
1810 */
1811function media_resize_image($file, $ext, $w, $h = 0)
1812{
1813    return media_mod_image($file, $ext, $w, $h, false);
1814}
1815
1816/**
1817 * Center crops the given image to the wanted size
1818 *
1819 * @author  Andreas Gohr <andi@splitbrain.org>
1820 *
1821 * @param string $file filename, path to file
1822 * @param string $ext  extension
1823 * @param int    $w    desired width
1824 * @param int    $h    desired height
1825 * @return string path to resized or original size if failed
1826 */
1827function media_crop_image($file, $ext, $w, $h = 0)
1828{
1829    return media_mod_image($file, $ext, $w, $h, true);
1830}
1831
1832/**
1833 * Calculate a token to be used to verify fetch requests for resized or
1834 * cropped images have been internally generated - and prevent external
1835 * DDOS attacks via fetch
1836 *
1837 * @author Christopher Smith <chris@jalakai.co.uk>
1838 *
1839 * @param string  $id    id of the image
1840 * @param int     $w     resize/crop width
1841 * @param int     $h     resize/crop height
1842 * @return string token or empty string if no token required
1843 */
1844function media_get_token($id, $w, $h)
1845{
1846    // token is only required for modified images
1847    if ($w || $h || media_isexternal($id)) {
1848        $token = $id;
1849        if ($w) $token .= '.'.$w;
1850        if ($h) $token .= '.'.$h;
1851
1852        return substr(PassHash::hmac('md5', $token, auth_cookiesalt()), 0, 6);
1853    }
1854
1855    return '';
1856}
1857
1858/**
1859 * Download a remote file and return local filename
1860 *
1861 * returns false if download fails. Uses cached file if available and
1862 * wanted
1863 *
1864 * @author  Andreas Gohr <andi@splitbrain.org>
1865 * @author  Pavel Vitis <Pavel.Vitis@seznam.cz>
1866 *
1867 * @param string $url
1868 * @param string $ext   extension
1869 * @param int    $cache cachetime in seconds
1870 * @return false|string path to cached file
1871 */
1872function media_get_from_URL($url, $ext, $cache)
1873{
1874    global $conf;
1875
1876    // if no cache or fetchsize just redirect
1877    if ($cache==0)           return false;
1878    if (!$conf['fetchsize']) return false;
1879
1880    $local = getCacheName(strtolower($url), ".media.$ext");
1881    $mtime = @filemtime($local); // 0 if not exists
1882
1883    //decide if download needed:
1884    if(($mtime == 0) || // cache does not exist
1885        ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired
1886    ) {
1887        if(media_image_download($url, $local)) {
1888            return $local;
1889        } else {
1890            return false;
1891        }
1892    }
1893
1894    //if cache exists use it else
1895    if($mtime) return $local;
1896
1897    //else return false
1898    return false;
1899}
1900
1901/**
1902 * Download image files
1903 *
1904 * @author Andreas Gohr <andi@splitbrain.org>
1905 *
1906 * @param string $url
1907 * @param string $file path to file in which to put the downloaded content
1908 * @return bool
1909 */
1910function media_image_download($url, $file)
1911{
1912    global $conf;
1913    $http = new DokuHTTPClient();
1914    $http->keep_alive = false; // we do single ops here, no need for keep-alive
1915
1916    $http->max_bodysize = $conf['fetchsize'];
1917    $http->timeout = 25; //max. 25 sec
1918    $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
1919
1920    $data = $http->get($url);
1921    if(!$data) return false;
1922
1923    $fileexists = file_exists($file);
1924    $fp = @fopen($file, "w");
1925    if(!$fp) return false;
1926    fwrite($fp, $data);
1927    fclose($fp);
1928    if(!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']);
1929
1930    // check if it is really an image
1931    $info = @getimagesize($file);
1932    if(!$info){
1933        @unlink($file);
1934        return false;
1935    }
1936
1937    return true;
1938}
1939
1940/**
1941 * resize images using external ImageMagick convert program
1942 *
1943 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
1944 * @author Andreas Gohr <andi@splitbrain.org>
1945 *
1946 * @param string $ext     extension
1947 * @param string $from    filename path to file
1948 * @param int    $from_w  original width
1949 * @param int    $from_h  original height
1950 * @param string $to      path to resized file
1951 * @param int    $to_w    desired width
1952 * @param int    $to_h    desired height
1953 * @return bool
1954 */
1955function media_resize_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h)
1956{
1957    global $conf;
1958
1959    // check if convert is configured
1960    if(!$conf['im_convert']) return false;
1961
1962    // prepare command
1963    $cmd  = $conf['im_convert'];
1964    $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
1965    if ($ext == 'jpg' || $ext == 'jpeg') {
1966        $cmd .= ' -quality '.$conf['jpg_quality'];
1967    }
1968    $cmd .= " $from $to";
1969
1970    @exec($cmd, $out, $retval);
1971    if ($retval == 0) return true;
1972    return false;
1973}
1974
1975/**
1976 * crop images using external ImageMagick convert program
1977 *
1978 * @author Andreas Gohr <andi@splitbrain.org>
1979 *
1980 * @param string $ext     extension
1981 * @param string $from    filename path to file
1982 * @param int    $from_w  original width
1983 * @param int    $from_h  original height
1984 * @param string $to      path to resized file
1985 * @param int    $to_w    desired width
1986 * @param int    $to_h    desired height
1987 * @param int    $ofs_x   offset of crop centre
1988 * @param int    $ofs_y   offset of crop centre
1989 * @return bool
1990 * @deprecated 2020-09-01
1991 */
1992function media_crop_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x, $ofs_y)
1993{
1994    global $conf;
1995    dbg_deprecated('splitbrain\\Slika');
1996
1997    // check if convert is configured
1998    if(!$conf['im_convert']) return false;
1999
2000    // prepare command
2001    $cmd  = $conf['im_convert'];
2002    $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y;
2003    if ($ext == 'jpg' || $ext == 'jpeg') {
2004        $cmd .= ' -quality '.$conf['jpg_quality'];
2005    }
2006    $cmd .= " $from $to";
2007
2008    @exec($cmd, $out, $retval);
2009    if ($retval == 0) return true;
2010    return false;
2011}
2012
2013/**
2014 * resize or crop images using PHP's libGD support
2015 *
2016 * @author Andreas Gohr <andi@splitbrain.org>
2017 * @author Sebastian Wienecke <s_wienecke@web.de>
2018 *
2019 * @param string $ext     extension
2020 * @param string $from    filename path to file
2021 * @param int    $from_w  original width
2022 * @param int    $from_h  original height
2023 * @param string $to      path to resized file
2024 * @param int    $to_w    desired width
2025 * @param int    $to_h    desired height
2026 * @param int    $ofs_x   offset of crop centre
2027 * @param int    $ofs_y   offset of crop centre
2028 * @return bool
2029 * @deprecated 2020-09-01
2030 */
2031function media_resize_imageGD($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x = 0, $ofs_y = 0)
2032{
2033    global $conf;
2034    dbg_deprecated('splitbrain\\Slika');
2035
2036    if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
2037
2038    // check available memory
2039    if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
2040        return false;
2041    }
2042
2043    // create an image of the given filetype
2044    $image = false;
2045    if ($ext == 'jpg' || $ext == 'jpeg'){
2046        if(!function_exists("imagecreatefromjpeg")) return false;
2047        $image = @imagecreatefromjpeg($from);
2048    }elseif($ext == 'png') {
2049        if(!function_exists("imagecreatefrompng")) return false;
2050        $image = @imagecreatefrompng($from);
2051
2052    }elseif($ext == 'gif') {
2053        if(!function_exists("imagecreatefromgif")) return false;
2054        $image = @imagecreatefromgif($from);
2055    }
2056    if(!$image) return false;
2057
2058    $newimg = false;
2059    if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){
2060        $newimg = @imagecreatetruecolor ($to_w, $to_h);
2061    }
2062    if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
2063    if(!$newimg){
2064        imagedestroy($image);
2065        return false;
2066    }
2067
2068    //keep png alpha channel if possible
2069    if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
2070        imagealphablending($newimg, false);
2071        imagesavealpha($newimg, true);
2072    }
2073
2074    //keep gif transparent color if possible
2075    if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
2076        if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
2077            $transcolorindex = @imagecolortransparent($image);
2078            if($transcolorindex >= 0 ) { //transparent color exists
2079                $transcolor = @imagecolorsforindex($image, $transcolorindex);
2080                $transcolorindex = @imagecolorallocate(
2081                    $newimg,
2082                    $transcolor['red'],
2083                    $transcolor['green'],
2084                    $transcolor['blue']
2085                );
2086                @imagefill($newimg, 0, 0, $transcolorindex);
2087                @imagecolortransparent($newimg, $transcolorindex);
2088            }else{ //filling with white
2089                $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2090                @imagefill($newimg, 0, 0, $whitecolorindex);
2091            }
2092        }else{ //filling with white
2093            $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2094            @imagefill($newimg, 0, 0, $whitecolorindex);
2095        }
2096    }
2097
2098    //try resampling first
2099    if(function_exists("imagecopyresampled")){
2100        if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) {
2101            imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2102        }
2103    }else{
2104        imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2105    }
2106
2107    $okay = false;
2108    if ($ext == 'jpg' || $ext == 'jpeg'){
2109        if(!function_exists('imagejpeg')){
2110            $okay = false;
2111        }else{
2112            $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
2113        }
2114    }elseif($ext == 'png') {
2115        if(!function_exists('imagepng')){
2116            $okay = false;
2117        }else{
2118            $okay =  imagepng($newimg, $to);
2119        }
2120    }elseif($ext == 'gif') {
2121        if(!function_exists('imagegif')){
2122            $okay = false;
2123        }else{
2124            $okay = imagegif($newimg, $to);
2125        }
2126    }
2127
2128    // destroy GD image resources
2129    imagedestroy($image);
2130    imagedestroy($newimg);
2131
2132    return $okay;
2133}
2134
2135/**
2136 * Return other media files with the same base name
2137 * but different extensions.
2138 *
2139 * @param string   $src     - ID of media file
2140 * @param string[] $exts    - alternative extensions to find other files for
2141 * @return array            - array(mime type => file ID)
2142 *
2143 * @author Anika Henke <anika@selfthinker.org>
2144 */
2145function media_alternativefiles($src, $exts)
2146{
2147
2148    $files = [];
2149    [$srcExt, ] = mimetype($src);
2150    $filebase = substr($src, 0, -1 * (strlen($srcExt)+1));
2151
2152    foreach($exts as $ext) {
2153        $fileid = $filebase.'.'.$ext;
2154        $file = mediaFN($fileid);
2155        if(file_exists($file)) {
2156            [, $fileMime] = mimetype($file);
2157            $files[$fileMime] = $fileid;
2158        }
2159    }
2160    return $files;
2161}
2162
2163/**
2164 * Check if video/audio is supported to be embedded.
2165 *
2166 * @param string $mime      - mimetype of media file
2167 * @param string $type      - type of media files to check ('video', 'audio', or null for all)
2168 * @return boolean
2169 *
2170 * @author Anika Henke <anika@selfthinker.org>
2171 */
2172function media_supportedav($mime, $type = null)
2173{
2174    $supportedAudio = [
2175        'ogg' => 'audio/ogg',
2176        'mp3' => 'audio/mpeg',
2177        'wav' => 'audio/wav'
2178    ];
2179    $supportedVideo = [
2180        'webm' => 'video/webm',
2181        'ogv' => 'video/ogg',
2182        'mp4' => 'video/mp4'
2183    ];
2184    if ($type == 'audio') {
2185        $supportedAv = $supportedAudio;
2186    } elseif ($type == 'video') {
2187        $supportedAv = $supportedVideo;
2188    } else {
2189        $supportedAv = array_merge($supportedAudio, $supportedVideo);
2190    }
2191    return in_array($mime, $supportedAv);
2192}
2193
2194/**
2195 * Return track media files with the same base name
2196 * but extensions that indicate kind and lang.
2197 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt...
2198 *
2199 * @param string   $src     - ID of media file
2200 * @return array            - array(mediaID => array( kind, srclang ))
2201 *
2202 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
2203 */
2204function media_trackfiles($src)
2205{
2206    $kinds=[
2207        'sub' => 'subtitles',
2208        'cap' => 'captions',
2209        'des' => 'descriptions',
2210        'cha' => 'chapters',
2211        'met' => 'metadata'
2212    ];
2213
2214    $files = [];
2215    $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/';
2216    $baseid=pathinfo($src, PATHINFO_FILENAME);
2217    $pattern=mediaFN($baseid).'.*.*.vtt';
2218    $list=glob($pattern);
2219    foreach($list as $track) {
2220        if(preg_match($re, $track, $matches)){
2221            $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=[$kinds[$matches[1]], $matches[2]];
2222        }
2223    }
2224    return $files;
2225}
2226
2227/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
2228