xref: /dokuwiki/inc/media.php (revision ba8f83490b45737d4cf33484c72b3aaa21e8894d)
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(
731            $data,
732            $conf['mediadir'],
733            'search_mediafiles',
734            ['showmsg'=>true, 'depth'=>1],
735            $dir,
736            1,
737            $sort
738        );
739
740        if(!count($data)){
741            echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
742        }else {
743            if ($fullscreenview) {
744                echo '<ul class="' . _media_get_list_type() . '">';
745            }
746            foreach($data as $item){
747                if (!$fullscreenview) {
748                    //FIXME old call: media_printfile($item,$auth,$jump);
749                    $display = new DisplayRow($item);
750                    $display->scrollIntoView($jump == $item->getID());
751                    $display->show();
752                } else {
753                    //FIXME old call: media_printfile_thumbs($item,$auth,$jump);
754                    echo '<li>';
755                    $display = new DisplayTile($item);
756                    $display->scrollIntoView($jump == $item->getID());
757                    $display->show();
758                    echo '</li>';
759                }
760            }
761            if ($fullscreenview) echo '</ul>'.NL;
762        }
763    }
764}
765
766/**
767 * Prints tabs for files list actions
768 *
769 * @author Kate Arzamastseva <pshns@ukr.net>
770 * @author Adrian Lang <mail@adrianlang.de>
771 *
772 * @param string $selected_tab - opened tab
773 */
774
775function media_tabs_files($selected_tab = '')
776{
777    global $lang;
778    $tabs = [];
779    foreach([
780                'files'  => 'mediaselect',
781                'upload' => 'media_uploadtab',
782                'search' => 'media_searchtab'
783            ] as $tab => $caption) {
784        $tabs[$tab] = [
785            'href'    => media_managerURL(['tab_files' => $tab], '&'),
786            'caption' => $lang[$caption]
787        ];
788    }
789
790    html_tabs($tabs, $selected_tab);
791}
792
793/**
794 * Prints tabs for files details actions
795 *
796 * @author Kate Arzamastseva <pshns@ukr.net>
797 * @param string $image filename of the current image
798 * @param string $selected_tab opened tab
799 */
800function media_tabs_details($image, $selected_tab = '')
801{
802    global $lang, $conf;
803
804    $tabs = [];
805    $tabs['view'] = [
806        'href'    => media_managerURL(['tab_details' => 'view'], '&'),
807        'caption' => $lang['media_viewtab']
808    ];
809
810    [, $mime] = mimetype($image);
811    if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) {
812        $tabs['edit'] = [
813            'href'    => media_managerURL(['tab_details' => 'edit'], '&'),
814            'caption' => $lang['media_edittab']
815        ];
816    }
817    if ($conf['mediarevisions']) {
818        $tabs['history'] = [
819            'href'    => media_managerURL(['tab_details' => 'history'], '&'),
820            'caption' => $lang['media_historytab']
821        ];
822    }
823
824    html_tabs($tabs, $selected_tab);
825}
826
827/**
828 * Prints options for the tab that displays a list of all files
829 *
830 * @author Kate Arzamastseva <pshns@ukr.net>
831 */
832function media_tab_files_options()
833{
834    global $lang;
835    global $INPUT;
836    global $ID;
837
838    $form = new Form([
839            'method' => 'get',
840            'action' => wl($ID),
841            'class' => 'options'
842    ]);
843    $form->addTagOpen('div')->addClass('no');
844    $form->setHiddenField('sectok', null);
845    $media_manager_params = media_managerURL([], '', false, true);
846    foreach ($media_manager_params as $pKey => $pVal) {
847        $form->setHiddenField($pKey, $pVal);
848    }
849    if ($INPUT->has('q')) {
850        $form->setHiddenField('q', $INPUT->str('q'));
851    }
852    $form->addHTML('<ul>'.NL);
853    foreach ([
854                 'list' => ['listType', ['thumbs', 'rows']],
855                 'sort' => ['sortBy', ['name', 'date']]
856             ] as $group => $content) {
857        $checked = "_media_get_{$group}_type";
858        $checked = $checked();
859
860        $form->addHTML('<li class="'. $content[0] .'">');
861        foreach ($content[1] as $option) {
862            $attrs = [];
863            if ($checked == $option) {
864                $attrs['checked'] = 'checked';
865            }
866            $radio = $form->addRadioButton(
867                $group.'_dwmedia',
868                $lang['media_'.$group.'_'.$option]
869            )->val($option)->id($content[0].'__'.$option)->addClass($option);
870            $radio->attrs($attrs);
871        }
872        $form->addHTML('</li>'.NL);
873    }
874    $form->addHTML('<li>');
875    $form->addButton('', $lang['btn_apply'])->attr('type', 'submit');
876    $form->addHTML('</li>'.NL);
877    $form->addHTML('</ul>'.NL);
878    $form->addTagClose('div');
879    print $form->toHTML();
880}
881
882/**
883 * Returns type of sorting for the list of files in media manager
884 *
885 * @author Kate Arzamastseva <pshns@ukr.net>
886 *
887 * @return string - sort type
888 */
889function _media_get_sort_type()
890{
891    return _media_get_display_param('sort', ['default' => 'name', 'date']);
892}
893
894/**
895 * Returns type of listing for the list of files in media manager
896 *
897 * @author Kate Arzamastseva <pshns@ukr.net>
898 *
899 * @return string - list type
900 */
901function _media_get_list_type()
902{
903    return _media_get_display_param('list', ['default' => 'thumbs', 'rows']);
904}
905
906/**
907 * Get display parameters
908 *
909 * @param string $param   name of parameter
910 * @param array  $values  allowed values, where default value has index key 'default'
911 * @return string the parameter value
912 */
913function _media_get_display_param($param, $values)
914{
915    global $INPUT;
916    if (in_array($INPUT->str($param), $values)) {
917        // FIXME: Set cookie
918        return $INPUT->str($param);
919    } else {
920        $val = get_doku_pref($param, $values['default']);
921        if (!in_array($val, $values)) {
922            $val = $values['default'];
923        }
924        return $val;
925    }
926}
927
928/**
929 * Prints tab that displays a list of all files
930 *
931 * @author Kate Arzamastseva <pshns@ukr.net>
932 *
933 * @param string    $ns
934 * @param null|int  $auth permission level
935 * @param string    $jump item id
936 */
937function media_tab_files($ns, $auth = null, $jump = '')
938{
939    global $lang;
940    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
941
942    if($auth < AUTH_READ){
943        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
944    }else{
945        media_filelist($ns, $auth, $jump, true, _media_get_sort_type());
946    }
947}
948
949/**
950 * Prints tab that displays uploading form
951 *
952 * @author Kate Arzamastseva <pshns@ukr.net>
953 *
954 * @param string   $ns
955 * @param null|int $auth permission level
956 * @param string   $jump item id
957 */
958function media_tab_upload($ns, $auth = null, $jump = '')
959{
960    global $lang;
961    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
962
963    echo '<div class="upload">'.NL;
964    if ($auth >= AUTH_UPLOAD) {
965        echo '<p>' . $lang['mediaupload'] . '</p>';
966    }
967    media_uploadform($ns, $auth, true);
968    echo '</div>'.NL;
969}
970
971/**
972 * Prints tab that displays search form
973 *
974 * @author Kate Arzamastseva <pshns@ukr.net>
975 *
976 * @param string $ns
977 * @param null|int $auth permission level
978 */
979function media_tab_search($ns, $auth = null)
980{
981    global $INPUT;
982
983    $do = $INPUT->str('mediado');
984    $query = $INPUT->str('q');
985    echo '<div class="search">'.NL;
986
987    media_searchform($ns, $query, true);
988    if ($do == 'searchlist' || $query) {
989        media_searchlist($query, $ns, $auth, true, _media_get_sort_type());
990    }
991    echo '</div>'.NL;
992}
993
994/**
995 * Prints tab that displays mediafile details
996 *
997 * @author Kate Arzamastseva <pshns@ukr.net>
998 *
999 * @param string     $image media id
1000 * @param string     $ns
1001 * @param null|int   $auth  permission level
1002 * @param string|int $rev   revision timestamp or empty string
1003 */
1004function media_tab_view($image, $ns, $auth = null, $rev = '')
1005{
1006    global $lang;
1007    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1008
1009    if ($image && $auth >= AUTH_READ) {
1010        $meta = new JpegMeta(mediaFN($image, $rev));
1011        media_preview($image, $auth, $rev, $meta);
1012        media_preview_buttons($image, $auth, $rev);
1013        media_details($image, $auth, $rev, $meta);
1014
1015    } else {
1016        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1017    }
1018}
1019
1020/**
1021 * Prints tab that displays form for editing mediafile metadata
1022 *
1023 * @author Kate Arzamastseva <pshns@ukr.net>
1024 *
1025 * @param string     $image media id
1026 * @param string     $ns
1027 * @param null|int   $auth permission level
1028 */
1029function media_tab_edit($image, $ns, $auth = null)
1030{
1031    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1032
1033    if ($image) {
1034        [, $mime] = mimetype($image);
1035        if ($mime == 'image/jpeg') media_metaform($image, $auth);
1036    }
1037}
1038
1039/**
1040 * Prints tab that displays mediafile revisions
1041 *
1042 * @author Kate Arzamastseva <pshns@ukr.net>
1043 *
1044 * @param string     $image media id
1045 * @param string     $ns
1046 * @param null|int   $auth permission level
1047 */
1048function media_tab_history($image, $ns, $auth = null)
1049{
1050    global $lang;
1051    global $INPUT;
1052
1053    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
1054    $do = $INPUT->str('mediado');
1055
1056    if ($auth >= AUTH_READ && $image) {
1057        if ($do == 'diff'){
1058            (new MediaDiff($image))->show(); //media_diff($image, $ns, $auth);
1059        } else {
1060            $first = $INPUT->int('first', -1);
1061            (new MediaRevisions($image))->show($first);
1062        }
1063    } else {
1064        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1065    }
1066}
1067
1068/**
1069 * Prints mediafile details
1070 *
1071 * @param string         $image media id
1072 * @param int            $auth permission level
1073 * @param int|string     $rev revision timestamp or empty string
1074 * @param JpegMeta|bool  $meta
1075 *
1076 * @author Kate Arzamastseva <pshns@ukr.net>
1077 */
1078function media_preview($image, $auth, $rev = '', $meta = false)
1079{
1080
1081    $size = media_image_preview_size($image, $rev, $meta);
1082
1083    if ($size) {
1084        global $lang;
1085        echo '<div class="image">';
1086
1087        $more = [];
1088        if ($rev) {
1089            $more['rev'] = $rev;
1090        } else {
1091            $t = @filemtime(mediaFN($image));
1092            $more['t'] = $t;
1093        }
1094
1095        $more['w'] = $size[0];
1096        $more['h'] = $size[1];
1097        $src = ml($image, $more);
1098
1099        echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">';
1100        echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
1101        echo '</a>';
1102
1103        echo '</div>';
1104    }
1105}
1106
1107/**
1108 * Prints mediafile action buttons
1109 *
1110 * @author Kate Arzamastseva <pshns@ukr.net>
1111 *
1112 * @param string     $image media id
1113 * @param int        $auth  permission level
1114 * @param int|string $rev   revision timestamp, or empty string
1115 */
1116function media_preview_buttons($image, $auth, $rev = '')
1117{
1118    global $lang, $conf;
1119
1120    echo '<ul class="actions">';
1121
1122    if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) {
1123
1124        // delete button
1125        $form = new Form([
1126            'id' => 'mediamanager__btn_delete',
1127            'action' => media_managerURL(['delete' => $image], '&'),
1128        ]);
1129        $form->addTagOpen('div')->addClass('no');
1130        $form->addButton('', $lang['btn_delete'])->attr('type', 'submit');
1131        $form->addTagClose('div');
1132        echo '<li>';
1133        echo $form->toHTML();
1134        echo '</li>';
1135    }
1136
1137    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1138    if ($auth >= $auth_ow && !$rev) {
1139
1140        // upload new version button
1141        $form = new Form([
1142            'id' => 'mediamanager__btn_update',
1143            'action' => media_managerURL(['image' => $image, 'mediado' => 'update'], '&'),
1144        ]);
1145        $form->addTagOpen('div')->addClass('no');
1146        $form->addButton('', $lang['media_update'])->attr('type', 'submit');
1147        $form->addTagClose('div');
1148        echo '<li>';
1149        echo $form->toHTML();
1150        echo '</li>';
1151    }
1152
1153    if ($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))) {
1154
1155        // restore button
1156        $form = new Form([
1157            'id' => 'mediamanager__btn_restore',
1158            'action'=>media_managerURL(['image' => $image], '&'),
1159        ]);
1160        $form->addTagOpen('div')->addClass('no');
1161        $form->setHiddenField('mediado', 'restore');
1162        $form->setHiddenField('rev', $rev);
1163        $form->addButton('', $lang['media_restore'])->attr('type', 'submit');
1164        $form->addTagClose('div');
1165        echo '<li>';
1166        echo $form->toHTML();
1167        echo '</li>';
1168    }
1169
1170    echo '</ul>';
1171}
1172
1173/**
1174 * Returns image width and height for mediamanager preview panel
1175 *
1176 * @author Kate Arzamastseva <pshns@ukr.net>
1177 * @param string         $image
1178 * @param int|string     $rev
1179 * @param JpegMeta|bool  $meta
1180 * @param int            $size
1181 * @return array
1182 */
1183function media_image_preview_size($image, $rev, $meta = false, $size = 500)
1184{
1185    if (!preg_match("/\.(jpe?g|gif|png)$/", $image)
1186      || !file_exists($filename = mediaFN($image, $rev))
1187    ) return [];
1188
1189    $info = getimagesize($filename);
1190    $w = $info[0];
1191    $h = $info[1];
1192
1193    if ($meta && ($w > $size || $h > $size)) {
1194        $ratio = $meta->getResizeRatio($size, $size);
1195        $w = floor($w * $ratio);
1196        $h = floor($h * $ratio);
1197    }
1198    return [$w, $h];
1199}
1200
1201/**
1202 * Returns the requested EXIF/IPTC tag from the image meta
1203 *
1204 * @author Kate Arzamastseva <pshns@ukr.net>
1205 *
1206 * @param array    $tags array with tags, first existing is returned
1207 * @param JpegMeta $meta
1208 * @param string   $alt  alternative value
1209 * @return string
1210 */
1211function media_getTag($tags, $meta = false, $alt = '')
1212{
1213    if (!$meta) return $alt;
1214    $info = $meta->getField($tags);
1215    if (!$info) return $alt;
1216    return $info;
1217}
1218
1219/**
1220 * Returns mediafile tags
1221 *
1222 * @author Kate Arzamastseva <pshns@ukr.net>
1223 *
1224 * @param JpegMeta $meta
1225 * @return array list of tags of the mediafile
1226 */
1227function media_file_tags($meta)
1228{
1229    // load the field descriptions
1230    static $fields = null;
1231    if (is_null($fields)) {
1232        $config_files = getConfigFiles('mediameta');
1233        foreach ($config_files as $config_file) {
1234            if (file_exists($config_file)) include($config_file);
1235        }
1236    }
1237
1238    $tags = [];
1239
1240    foreach ($fields as $tag) {
1241        $t = [];
1242        if (!empty($tag[0])) $t = [$tag[0]];
1243        if (isset($tag[3]) && is_array($tag[3])) $t = array_merge($t, $tag[3]);
1244        $value = media_getTag($t, $meta);
1245        $tags[] = ['tag' => $tag, 'value' => $value];
1246    }
1247
1248    return $tags;
1249}
1250
1251/**
1252 * Prints mediafile tags
1253 *
1254 * @author Kate Arzamastseva <pshns@ukr.net>
1255 *
1256 * @param string        $image image id
1257 * @param int           $auth  permission level
1258 * @param string|int    $rev   revision timestamp, or empty string
1259 * @param bool|JpegMeta $meta  image object, or create one if false
1260 */
1261function media_details($image, $auth, $rev = '', $meta = false)
1262{
1263    global $lang;
1264
1265    if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
1266    $tags = media_file_tags($meta);
1267
1268    echo '<dl>'.NL;
1269    foreach($tags as $tag){
1270        if ($tag['value']) {
1271            $value = cleanText($tag['value']);
1272            echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>';
1273            if ($tag['tag'][2] == 'date') echo dformat($value);
1274            else echo hsc($value);
1275            echo '</dd>'.NL;
1276        }
1277    }
1278    echo '</dl>'.NL;
1279    echo '<dl>'.NL;
1280    echo '<dt>'.$lang['reference'].':</dt>';
1281    $media_usage = ft_mediause($image, true);
1282    if($media_usage !== []){
1283        foreach($media_usage as $path){
1284            echo '<dd>'.html_wikilink($path).'</dd>';
1285        }
1286    }else{
1287        echo '<dd>'.$lang['nothingfound'].'</dd>';
1288    }
1289    echo '</dl>'.NL;
1290
1291}
1292
1293/**
1294 * Shows difference between two revisions of file
1295 *
1296 * @author Kate Arzamastseva <pshns@ukr.net>
1297 *
1298 * @param string $image  image id
1299 * @param string $ns
1300 * @param int $auth permission level
1301 * @param bool $fromajax
1302 *
1303 * @deprecated 2020-12-31
1304 */
1305function media_diff($image, $ns, $auth, $fromajax = false)
1306{
1307    dbg_deprecated('see '. MediaDiff::class .'::show()');
1308}
1309
1310/**
1311 * Callback for media file diff
1312 *
1313 * @param array $data event data
1314 *
1315 * @deprecated 2020-12-31
1316 */
1317function _media_file_diff($data)
1318{
1319    dbg_deprecated('see '. MediaDiff::class .'::show()');
1320}
1321
1322/**
1323 * Shows difference between two revisions of image
1324 *
1325 * @author Kate Arzamastseva <pshns@ukr.net>
1326 *
1327 * @param string $image
1328 * @param string|int $l_rev revision timestamp, or empty string
1329 * @param string|int $r_rev revision timestamp, or empty string
1330 * @param string $ns
1331 * @param int $auth permission level
1332 * @param bool $fromajax
1333 * @deprecated 2020-12-31
1334 */
1335function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax)
1336{
1337    dbg_deprecated('see '. MediaDiff::class .'::showFileDiff()');
1338}
1339
1340/**
1341 * Prints two images side by side
1342 * and slider
1343 *
1344 * @author Kate Arzamastseva <pshns@ukr.net>
1345 *
1346 * @param string $image   image id
1347 * @param int    $l_rev   revision timestamp, or empty string
1348 * @param int    $r_rev   revision timestamp, or empty string
1349 * @param array  $l_size  array with width and height
1350 * @param array  $r_size  array with width and height
1351 * @param string $type
1352 * @deprecated 2020-12-31
1353 */
1354function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type)
1355{
1356    dbg_deprecated('see '. MediaDiff::class .'::showImageDiff()');
1357}
1358
1359/**
1360 * Restores an old revision of a media file
1361 *
1362 * @param string $image media id
1363 * @param int    $rev   revision timestamp or empty string
1364 * @param int    $auth
1365 * @return string - file's id
1366 *
1367 * @author Kate Arzamastseva <pshns@ukr.net>
1368 */
1369function media_restore($image, $rev, $auth)
1370{
1371    global $conf;
1372    if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
1373    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
1374    if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
1375    if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
1376    [, $imime, ] = mimetype($image);
1377    $res = media_upload_finish(
1378        mediaFN($image, $rev),
1379        mediaFN($image),
1380        $image,
1381        $imime,
1382        true,
1383        'copy'
1384    );
1385    if (is_array($res)) {
1386        msg($res[0], $res[1]);
1387        return false;
1388    }
1389    return $res;
1390}
1391
1392/**
1393 * List all files found by the search request
1394 *
1395 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1396 * @author Andreas Gohr <gohr@cosmocode.de>
1397 * @author Kate Arzamastseva <pshns@ukr.net>
1398 * @triggers MEDIA_SEARCH
1399 *
1400 * @param string $query
1401 * @param string $ns
1402 * @param null|int $auth
1403 * @param bool $fullscreen
1404 * @param string $sort
1405 */
1406function media_searchlist($query, $ns, $auth = null, $fullscreen = false, $sort = 'natural')
1407{
1408    global $conf;
1409    global $lang;
1410
1411    $ns = cleanID($ns);
1412    $evdata = [
1413        'ns'    => $ns,
1414        'data'  => [],
1415        'query' => $query
1416    ];
1417    if (!blank($query)) {
1418        $evt = new Event('MEDIA_SEARCH', $evdata);
1419        if ($evt->advise_before()) {
1420            $dir = utf8_encodeFN(str_replace(':', '/', $evdata['ns']));
1421            $quoted = preg_quote($evdata['query'], '/');
1422            //apply globbing
1423            $quoted = str_replace(['\*', '\?'], ['.*', '.'], $quoted, $count);
1424
1425            //if we use globbing file name must match entirely but may be preceded by arbitrary namespace
1426            if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$';
1427
1428            $pattern = '/'.$quoted.'/i';
1429            search(
1430                $evdata['data'],
1431                $conf['mediadir'],
1432                'search_mediafiles',
1433                ['showmsg'=>false, 'pattern'=>$pattern],
1434                $dir,
1435                1,
1436                $sort
1437            );
1438        }
1439        $evt->advise_after();
1440        unset($evt);
1441    }
1442
1443    if (!$fullscreen) {
1444        echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'], hsc($ns).':*').'</h1>'.NL;
1445        media_searchform($ns, $query);
1446    }
1447
1448    if(!count($evdata['data'])){
1449        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
1450    }else {
1451        if ($fullscreen) {
1452            echo '<ul class="' . _media_get_list_type() . '">';
1453        }
1454        foreach($evdata['data'] as $item){
1455            if (!$fullscreen) {
1456                // FIXME old call: media_printfile($item,$item['perm'],'',true);
1457                $display = new DisplayRow($item);
1458                $display->relativeDisplay($ns);
1459                $display->show();
1460            } else {
1461                // FIXME old call: media_printfile_thumbs($item,$item['perm'],false,true);
1462                $display = new DisplayTile($item);
1463                $display->relativeDisplay($ns);
1464                echo '<li>';
1465                $display->show();
1466                echo '</li>';
1467            }
1468        }
1469        if ($fullscreen) echo '</ul>'.NL;
1470    }
1471}
1472
1473/**
1474 * Display a media icon
1475 *
1476 * @param string $filename media id
1477 * @param string $size     the size subfolder, if not specified 16x16 is used
1478 * @return string html
1479 */
1480function media_printicon($filename, $size = '')
1481{
1482    [$ext] = mimetype(mediaFN($filename), false);
1483
1484    if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) {
1485        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png';
1486    } else {
1487        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png';
1488    }
1489
1490    return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
1491}
1492
1493/**
1494 * Build link based on the current, adding/rewriting parameters
1495 *
1496 * @author Kate Arzamastseva <pshns@ukr.net>
1497 *
1498 * @param array|bool $params
1499 * @param string     $amp           separator
1500 * @param bool       $abs           absolute url?
1501 * @param bool       $params_array  return the parmeters array?
1502 * @return string|array - link or link parameters
1503 */
1504function media_managerURL($params = false, $amp = '&amp;', $abs = false, $params_array = false)
1505{
1506    global $ID;
1507    global $INPUT;
1508
1509    $gets = ['do' => 'media'];
1510    $media_manager_params = ['tab_files', 'tab_details', 'image', 'ns', 'list', 'sort'];
1511    foreach ($media_manager_params as $x) {
1512        if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x);
1513    }
1514
1515    if ($params) {
1516        $gets = $params + $gets;
1517    }
1518    unset($gets['id']);
1519    if (isset($gets['delete'])) {
1520        unset($gets['image']);
1521        unset($gets['tab_details']);
1522    }
1523
1524    if ($params_array) return $gets;
1525
1526    return wl($ID, $gets, $abs, $amp);
1527}
1528
1529/**
1530 * Print the media upload form if permissions are correct
1531 *
1532 * @author Andreas Gohr <andi@splitbrain.org>
1533 * @author Kate Arzamastseva <pshns@ukr.net>
1534 *
1535 * @param string $ns
1536 * @param int    $auth permission level
1537 * @param bool  $fullscreen
1538 */
1539function media_uploadform($ns, $auth, $fullscreen = false)
1540{
1541    global $lang;
1542    global $conf;
1543    global $INPUT;
1544
1545    if ($auth < AUTH_UPLOAD) {
1546        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
1547        return;
1548    }
1549    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1550
1551    $update = false;
1552    $id = '';
1553    if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') {
1554        $update = true;
1555        $id = cleanID($INPUT->str('image'));
1556    }
1557
1558    // The default HTML upload form
1559    $form = new Form([
1560        'id' => 'dw__upload',
1561        'enctype' => 'multipart/form-data',
1562        'action' => ($fullscreen)
1563                    ? media_managerURL(['tab_files' => 'files', 'tab_details' => 'view'], '&')
1564                    : DOKU_BASE.'lib/exe/mediamanager.php',
1565    ]);
1566    $form->addTagOpen('div')->addClass('no');
1567    $form->setHiddenField('ns', hsc($ns));  // FIXME hsc required?
1568    $form->addTagOpen('p');
1569    $form->addTextInput('upload', $lang['txt_upload'])->id('upload__file')
1570            ->attrs(['type' => 'file']);
1571    $form->addTagClose('p');
1572    $form->addTagOpen('p');
1573    $form->addTextInput('mediaid', $lang['txt_filename'])->id('upload__name')
1574            ->val(noNS($id));
1575    $form->addButton('', $lang['btn_upload'])->attr('type', 'submit');
1576    $form->addTagClose('p');
1577    if ($auth >= $auth_ow){
1578        $form->addTagOpen('p');
1579        $attrs = [];
1580        if ($update) $attrs['checked'] = 'checked';
1581        $form->addCheckbox('ow', $lang['txt_overwrt'])->id('dw__ow')->val('1')
1582            ->addClass('check')->attrs($attrs);
1583        $form->addTagClose('p');
1584    }
1585    $form->addTagClose('div');
1586
1587    if (!$fullscreen) {
1588        echo '<div class="upload">'. $lang['mediaupload'] .'</div>'.DOKU_LF;
1589    } else {
1590        echo DOKU_LF;
1591    }
1592
1593    echo '<div id="mediamanager__uploader">'.DOKU_LF;
1594    echo $form->toHTML('Upload');
1595    echo '</div>'.DOKU_LF;
1596
1597    echo '<p class="maxsize">';
1598    printf($lang['maxuploadsize'], filesize_h(media_getuploadsize()));
1599    echo ' <a class="allowedmime" href="#">'. $lang['allowedmime'] .'</a>';
1600    echo ' <span>'. implode(', ', array_keys(getMimeTypes())) .'</span>';
1601    echo '</p>'.DOKU_LF;
1602}
1603
1604/**
1605 * Returns the size uploaded files may have
1606 *
1607 * This uses a conservative approach using the lowest number found
1608 * in any of the limiting ini settings
1609 *
1610 * @returns int size in bytes
1611 */
1612function media_getuploadsize()
1613{
1614    $okay = 0;
1615
1616    $post = php_to_byte(@ini_get('post_max_size'));
1617    $suho = php_to_byte(@ini_get('suhosin.post.max_value_length'));
1618    $upld = php_to_byte(@ini_get('upload_max_filesize'));
1619
1620    if($post && ($post < $okay || $okay === 0)) $okay = $post;
1621    if($suho && ($suho < $okay || $okay == 0)) $okay = $suho;
1622    if($upld && ($upld < $okay || $okay == 0)) $okay = $upld;
1623
1624    return $okay;
1625}
1626
1627/**
1628 * Print the search field form
1629 *
1630 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1631 * @author Kate Arzamastseva <pshns@ukr.net>
1632 *
1633 * @param string $ns
1634 * @param string $query
1635 * @param bool $fullscreen
1636 */
1637function media_searchform($ns, $query = '', $fullscreen = false)
1638{
1639    global $lang;
1640
1641    // The default HTML search form
1642    $form = new Form([
1643        'id'     => 'dw__mediasearch',
1644        'action' => ($fullscreen)
1645                    ? media_managerURL([], '&')
1646                    : DOKU_BASE.'lib/exe/mediamanager.php',
1647    ]);
1648    $form->addTagOpen('div')->addClass('no');
1649    $form->setHiddenField('ns', $ns);
1650    $form->setHiddenField($fullscreen ? 'mediado' : 'do', 'searchlist');
1651
1652    $form->addTagOpen('p');
1653    $form->addTextInput('q', $lang['searchmedia'])
1654            ->attr('title', sprintf($lang['searchmedia_in'], hsc($ns) .':*'))
1655            ->val($query);
1656    $form->addHTML(' ');
1657    $form->addButton('', $lang['btn_search'])->attr('type', 'submit');
1658    $form->addTagClose('p');
1659    $form->addTagClose('div');
1660    print $form->toHTML('SearchMedia');
1661}
1662
1663/**
1664 * Build a tree outline of available media namespaces
1665 *
1666 * @author Andreas Gohr <andi@splitbrain.org>
1667 *
1668 * @param string $ns
1669 */
1670function media_nstree($ns)
1671{
1672    global $conf;
1673    global $lang;
1674
1675    // currently selected namespace
1676    $ns  = cleanID($ns);
1677    if(empty($ns)){
1678        global $ID;
1679        $ns = (string)getNS($ID);
1680    }
1681
1682    $ns_dir  = utf8_encodeFN(str_replace(':', '/', $ns));
1683
1684    $data = [];
1685    search($data, $conf['mediadir'], 'search_index', ['ns' => $ns_dir, 'nofiles' => true]);
1686
1687    // wrap a list with the root level around the other namespaces
1688    array_unshift($data, ['level' => 0, 'id' => '', 'open' =>'true', 'label' => '['.$lang['mediaroot'].']']);
1689
1690    // insert the current ns into the hierarchy if it isn't already part of it
1691    $ns_parts = explode(':', $ns);
1692    $tmp_ns = '';
1693    $pos = 0;
1694    foreach ($ns_parts as $level => $part) {
1695        if ($tmp_ns) $tmp_ns .= ':'.$part;
1696        else $tmp_ns = $part;
1697
1698        // find the namespace parts or insert them
1699        while ($data[$pos]['id'] != $tmp_ns) {
1700            if (
1701                $pos >= count($data) ||
1702                ($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
1703            ) {
1704                array_splice($data, $pos, 0, [['level' => $level+1, 'id' => $tmp_ns, 'open' => 'true']]);
1705                break;
1706            }
1707            ++$pos;
1708        }
1709    }
1710
1711    echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li');
1712}
1713
1714/**
1715 * Userfunction for html_buildlist
1716 *
1717 * Prints a media namespace tree item
1718 *
1719 * @author Andreas Gohr <andi@splitbrain.org>
1720 *
1721 * @param array $item
1722 * @return string html
1723 */
1724function media_nstree_item($item)
1725{
1726    global $INPUT;
1727    $pos   = strrpos($item['id'], ':');
1728    $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0);
1729    if(empty($item['label'])) $item['label'] = $label;
1730
1731    $ret  = '';
1732    if ($INPUT->str('do') != 'media')
1733    $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
1734    else $ret .= '<a href="'.media_managerURL(['ns' => idfilter($item['id'], false), 'tab_files' => 'files'])
1735        .'" class="idx_dir">';
1736    $ret .= $item['label'];
1737    $ret .= '</a>';
1738    return $ret;
1739}
1740
1741/**
1742 * Userfunction for html_buildlist
1743 *
1744 * Prints a media namespace tree item opener
1745 *
1746 * @author Andreas Gohr <andi@splitbrain.org>
1747 *
1748 * @param array $item
1749 * @return string html
1750 */
1751function media_nstree_li($item)
1752{
1753    $class='media level'.$item['level'];
1754    if($item['open']){
1755        $class .= ' open';
1756        $img   = DOKU_BASE.'lib/images/minus.gif';
1757        $alt   = '−';
1758    }else{
1759        $class .= ' closed';
1760        $img   = DOKU_BASE.'lib/images/plus.gif';
1761        $alt   = '+';
1762    }
1763    // TODO: only deliver an image if it actually has a subtree...
1764    return '<li class="'.$class.'">'.
1765        '<img src="'.$img.'" alt="'.$alt.'" />';
1766}
1767
1768/**
1769 * Resizes or crop the given image to the given size
1770 *
1771 * @author  Andreas Gohr <andi@splitbrain.org>
1772 *
1773 * @param string $file filename, path to file
1774 * @param string $ext  extension
1775 * @param int    $w    desired width
1776 * @param int    $h    desired height
1777 * @param bool   $crop should a center crop be used?
1778 * @return string path to resized or original size if failed
1779 */
1780function media_mod_image($file, $ext, $w, $h = 0, $crop = false)
1781{
1782    global $conf;
1783    if(!$h) $h = 0;
1784    // we wont scale up to infinity
1785    if($w > 2000 || $h > 2000) return $file;
1786
1787    $operation = $crop ? 'crop' : 'resize';
1788
1789    $options = [
1790        'quality' => $conf['jpg_quality'],
1791        'imconvert' => $conf['im_convert'],
1792    ];
1793
1794    $cache = new CacheImageMod($file, $w, $h, $ext, $crop);
1795    if(!$cache->useCache()) {
1796        try {
1797            Slika::run($file, $options)
1798                 ->autorotate()
1799                 ->$operation($w, $h)
1800                 ->save($cache->cache, $ext);
1801            if($conf['fperm']) @chmod($cache->cache, $conf['fperm']);
1802        } catch (Exception $e) {
1803            Logger::debug($e->getMessage());
1804            return $file;
1805        }
1806    }
1807
1808    return $cache->cache;
1809}
1810
1811/**
1812 * Resizes the given image to the given size
1813 *
1814 * @author  Andreas Gohr <andi@splitbrain.org>
1815 *
1816 * @param string $file filename, path to file
1817 * @param string $ext  extension
1818 * @param int    $w    desired width
1819 * @param int    $h    desired height
1820 * @return string path to resized or original size if failed
1821 */
1822function media_resize_image($file, $ext, $w, $h = 0)
1823{
1824    return media_mod_image($file, $ext, $w, $h, false);
1825}
1826
1827/**
1828 * Center crops the given image to the wanted size
1829 *
1830 * @author  Andreas Gohr <andi@splitbrain.org>
1831 *
1832 * @param string $file filename, path to file
1833 * @param string $ext  extension
1834 * @param int    $w    desired width
1835 * @param int    $h    desired height
1836 * @return string path to resized or original size if failed
1837 */
1838function media_crop_image($file, $ext, $w, $h = 0)
1839{
1840    return media_mod_image($file, $ext, $w, $h, true);
1841}
1842
1843/**
1844 * Calculate a token to be used to verify fetch requests for resized or
1845 * cropped images have been internally generated - and prevent external
1846 * DDOS attacks via fetch
1847 *
1848 * @author Christopher Smith <chris@jalakai.co.uk>
1849 *
1850 * @param string  $id    id of the image
1851 * @param int     $w     resize/crop width
1852 * @param int     $h     resize/crop height
1853 * @return string token or empty string if no token required
1854 */
1855function media_get_token($id, $w, $h)
1856{
1857    // token is only required for modified images
1858    if ($w || $h || media_isexternal($id)) {
1859        $token = $id;
1860        if ($w) $token .= '.'.$w;
1861        if ($h) $token .= '.'.$h;
1862
1863        return substr(PassHash::hmac('md5', $token, auth_cookiesalt()), 0, 6);
1864    }
1865
1866    return '';
1867}
1868
1869/**
1870 * Download a remote file and return local filename
1871 *
1872 * returns false if download fails. Uses cached file if available and
1873 * wanted
1874 *
1875 * @author  Andreas Gohr <andi@splitbrain.org>
1876 * @author  Pavel Vitis <Pavel.Vitis@seznam.cz>
1877 *
1878 * @param string $url
1879 * @param string $ext   extension
1880 * @param int    $cache cachetime in seconds
1881 * @return false|string path to cached file
1882 */
1883function media_get_from_URL($url, $ext, $cache)
1884{
1885    global $conf;
1886
1887    // if no cache or fetchsize just redirect
1888    if ($cache==0)           return false;
1889    if (!$conf['fetchsize']) return false;
1890
1891    $local = getCacheName(strtolower($url), ".media.$ext");
1892    $mtime = @filemtime($local); // 0 if not exists
1893
1894    //decide if download needed:
1895    if(($mtime == 0) || // cache does not exist
1896        ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired
1897    ) {
1898        if(media_image_download($url, $local)) {
1899            return $local;
1900        } else {
1901            return false;
1902        }
1903    }
1904
1905    //if cache exists use it else
1906    if($mtime) return $local;
1907
1908    //else return false
1909    return false;
1910}
1911
1912/**
1913 * Download image files
1914 *
1915 * @author Andreas Gohr <andi@splitbrain.org>
1916 *
1917 * @param string $url
1918 * @param string $file path to file in which to put the downloaded content
1919 * @return bool
1920 */
1921function media_image_download($url, $file)
1922{
1923    global $conf;
1924    $http = new DokuHTTPClient();
1925    $http->keep_alive = false; // we do single ops here, no need for keep-alive
1926
1927    $http->max_bodysize = $conf['fetchsize'];
1928    $http->timeout = 25; //max. 25 sec
1929    $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
1930
1931    $data = $http->get($url);
1932    if(!$data) return false;
1933
1934    $fileexists = file_exists($file);
1935    $fp = @fopen($file, "w");
1936    if(!$fp) return false;
1937    fwrite($fp, $data);
1938    fclose($fp);
1939    if(!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']);
1940
1941    // check if it is really an image
1942    $info = @getimagesize($file);
1943    if(!$info){
1944        @unlink($file);
1945        return false;
1946    }
1947
1948    return true;
1949}
1950
1951/**
1952 * resize images using external ImageMagick convert program
1953 *
1954 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
1955 * @author Andreas Gohr <andi@splitbrain.org>
1956 *
1957 * @param string $ext     extension
1958 * @param string $from    filename path to file
1959 * @param int    $from_w  original width
1960 * @param int    $from_h  original height
1961 * @param string $to      path to resized file
1962 * @param int    $to_w    desired width
1963 * @param int    $to_h    desired height
1964 * @return bool
1965 */
1966function media_resize_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h)
1967{
1968    global $conf;
1969
1970    // check if convert is configured
1971    if(!$conf['im_convert']) return false;
1972
1973    // prepare command
1974    $cmd  = $conf['im_convert'];
1975    $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
1976    if ($ext == 'jpg' || $ext == 'jpeg') {
1977        $cmd .= ' -quality '.$conf['jpg_quality'];
1978    }
1979    $cmd .= " $from $to";
1980
1981    @exec($cmd, $out, $retval);
1982    if ($retval == 0) return true;
1983    return false;
1984}
1985
1986/**
1987 * crop images using external ImageMagick convert program
1988 *
1989 * @author Andreas Gohr <andi@splitbrain.org>
1990 *
1991 * @param string $ext     extension
1992 * @param string $from    filename path to file
1993 * @param int    $from_w  original width
1994 * @param int    $from_h  original height
1995 * @param string $to      path to resized file
1996 * @param int    $to_w    desired width
1997 * @param int    $to_h    desired height
1998 * @param int    $ofs_x   offset of crop centre
1999 * @param int    $ofs_y   offset of crop centre
2000 * @return bool
2001 * @deprecated 2020-09-01
2002 */
2003function media_crop_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x, $ofs_y)
2004{
2005    global $conf;
2006    dbg_deprecated('splitbrain\\Slika');
2007
2008    // check if convert is configured
2009    if(!$conf['im_convert']) return false;
2010
2011    // prepare command
2012    $cmd  = $conf['im_convert'];
2013    $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y;
2014    if ($ext == 'jpg' || $ext == 'jpeg') {
2015        $cmd .= ' -quality '.$conf['jpg_quality'];
2016    }
2017    $cmd .= " $from $to";
2018
2019    @exec($cmd, $out, $retval);
2020    if ($retval == 0) return true;
2021    return false;
2022}
2023
2024/**
2025 * resize or crop images using PHP's libGD support
2026 *
2027 * @author Andreas Gohr <andi@splitbrain.org>
2028 * @author Sebastian Wienecke <s_wienecke@web.de>
2029 *
2030 * @param string $ext     extension
2031 * @param string $from    filename path to file
2032 * @param int    $from_w  original width
2033 * @param int    $from_h  original height
2034 * @param string $to      path to resized file
2035 * @param int    $to_w    desired width
2036 * @param int    $to_h    desired height
2037 * @param int    $ofs_x   offset of crop centre
2038 * @param int    $ofs_y   offset of crop centre
2039 * @return bool
2040 * @deprecated 2020-09-01
2041 */
2042function media_resize_imageGD($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x = 0, $ofs_y = 0)
2043{
2044    global $conf;
2045    dbg_deprecated('splitbrain\\Slika');
2046
2047    if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
2048
2049    // check available memory
2050    if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
2051        return false;
2052    }
2053
2054    // create an image of the given filetype
2055    $image = false;
2056    if ($ext == 'jpg' || $ext == 'jpeg'){
2057        if(!function_exists("imagecreatefromjpeg")) return false;
2058        $image = @imagecreatefromjpeg($from);
2059    }elseif($ext == 'png') {
2060        if(!function_exists("imagecreatefrompng")) return false;
2061        $image = @imagecreatefrompng($from);
2062
2063    }elseif($ext == 'gif') {
2064        if(!function_exists("imagecreatefromgif")) return false;
2065        $image = @imagecreatefromgif($from);
2066    }
2067    if(!$image) return false;
2068
2069    $newimg = false;
2070    if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){
2071        $newimg = @imagecreatetruecolor($to_w, $to_h);
2072    }
2073    if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
2074    if(!$newimg){
2075        imagedestroy($image);
2076        return false;
2077    }
2078
2079    //keep png alpha channel if possible
2080    if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
2081        imagealphablending($newimg, false);
2082        imagesavealpha($newimg, true);
2083    }
2084
2085    //keep gif transparent color if possible
2086    if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
2087        if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
2088            $transcolorindex = @imagecolortransparent($image);
2089            if($transcolorindex >= 0 ) { //transparent color exists
2090                $transcolor = @imagecolorsforindex($image, $transcolorindex);
2091                $transcolorindex = @imagecolorallocate(
2092                    $newimg,
2093                    $transcolor['red'],
2094                    $transcolor['green'],
2095                    $transcolor['blue']
2096                );
2097                @imagefill($newimg, 0, 0, $transcolorindex);
2098                @imagecolortransparent($newimg, $transcolorindex);
2099            }else{ //filling with white
2100                $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2101                @imagefill($newimg, 0, 0, $whitecolorindex);
2102            }
2103        }else{ //filling with white
2104            $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2105            @imagefill($newimg, 0, 0, $whitecolorindex);
2106        }
2107    }
2108
2109    //try resampling first
2110    if(function_exists("imagecopyresampled")){
2111        if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) {
2112            imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2113        }
2114    }else{
2115        imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2116    }
2117
2118    $okay = false;
2119    if ($ext == 'jpg' || $ext == 'jpeg'){
2120        if(!function_exists('imagejpeg')){
2121            $okay = false;
2122        }else{
2123            $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
2124        }
2125    }elseif($ext == 'png') {
2126        if(!function_exists('imagepng')){
2127            $okay = false;
2128        }else{
2129            $okay =  imagepng($newimg, $to);
2130        }
2131    }elseif($ext == 'gif') {
2132        if(!function_exists('imagegif')){
2133            $okay = false;
2134        }else{
2135            $okay = imagegif($newimg, $to);
2136        }
2137    }
2138
2139    // destroy GD image resources
2140    imagedestroy($image);
2141    imagedestroy($newimg);
2142
2143    return $okay;
2144}
2145
2146/**
2147 * Return other media files with the same base name
2148 * but different extensions.
2149 *
2150 * @param string   $src     - ID of media file
2151 * @param string[] $exts    - alternative extensions to find other files for
2152 * @return array            - array(mime type => file ID)
2153 *
2154 * @author Anika Henke <anika@selfthinker.org>
2155 */
2156function media_alternativefiles($src, $exts)
2157{
2158
2159    $files = [];
2160    [$srcExt, ] = mimetype($src);
2161    $filebase = substr($src, 0, -1 * (strlen($srcExt)+1));
2162
2163    foreach($exts as $ext) {
2164        $fileid = $filebase.'.'.$ext;
2165        $file = mediaFN($fileid);
2166        if(file_exists($file)) {
2167            [, $fileMime] = mimetype($file);
2168            $files[$fileMime] = $fileid;
2169        }
2170    }
2171    return $files;
2172}
2173
2174/**
2175 * Check if video/audio is supported to be embedded.
2176 *
2177 * @param string $mime      - mimetype of media file
2178 * @param string $type      - type of media files to check ('video', 'audio', or null for all)
2179 * @return boolean
2180 *
2181 * @author Anika Henke <anika@selfthinker.org>
2182 */
2183function media_supportedav($mime, $type = null)
2184{
2185    $supportedAudio = [
2186        'ogg' => 'audio/ogg',
2187        'mp3' => 'audio/mpeg',
2188        'wav' => 'audio/wav'
2189    ];
2190    $supportedVideo = [
2191        'webm' => 'video/webm',
2192        'ogv' => 'video/ogg',
2193        'mp4' => 'video/mp4'
2194    ];
2195    if ($type == 'audio') {
2196        $supportedAv = $supportedAudio;
2197    } elseif ($type == 'video') {
2198        $supportedAv = $supportedVideo;
2199    } else {
2200        $supportedAv = array_merge($supportedAudio, $supportedVideo);
2201    }
2202    return in_array($mime, $supportedAv);
2203}
2204
2205/**
2206 * Return track media files with the same base name
2207 * but extensions that indicate kind and lang.
2208 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt...
2209 *
2210 * @param string   $src     - ID of media file
2211 * @return array            - array(mediaID => array( kind, srclang ))
2212 *
2213 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
2214 */
2215function media_trackfiles($src)
2216{
2217    $kinds=[
2218        'sub' => 'subtitles',
2219        'cap' => 'captions',
2220        'des' => 'descriptions',
2221        'cha' => 'chapters',
2222        'met' => 'metadata'
2223    ];
2224
2225    $files = [];
2226    $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/';
2227    $baseid=pathinfo($src, PATHINFO_FILENAME);
2228    $pattern=mediaFN($baseid).'.*.*.vtt';
2229    $list=glob($pattern);
2230    foreach($list as $track) {
2231        if(preg_match($re, $track, $matches)){
2232            $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=[$kinds[$matches[1]], $matches[2]];
2233        }
2234    }
2235    return $files;
2236}
2237
2238/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
2239