Failed Conditions
Push — mediaitems ( 8f7d0e...4f33ba )
by Andreas
03:03
created

inc/media.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

Code
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 <[email protected]>
7
 */
8
9
use dokuwiki\ChangeLog\MediaChangeLog;
10
use dokuwiki\HTTP\DokuHTTPClient;
11
use dokuwiki\Subscriptions\MediaSubscriptionSender;
12
use dokuwiki\Extension\Event;
13
use dokuwiki\Form\Form;
14
use dokuwiki\Utf8\Sort;
15
16
/**
17
 * Lists pages which currently use a media file selected for deletion
18
 *
19
 * References uses the same visual as search results and share
20
 * their CSS tags except pagenames won't be links.
21
 *
22
 * @author Matthias Grimm <[email protected]>
23
 *
24
 * @param array $data
25
 * @param string $id
26
 */
27
function media_filesinuse($data,$id){
28
    global $lang;
29
    echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>';
30
    echo '<p>'.hsc($lang['ref_inuse']).'</p>';
31
32
    $hidden=0; //count of hits without read permission
33
    foreach($data as $row){
34
        if(auth_quickaclcheck($row) >= AUTH_READ && isVisiblePage($row)){
35
            echo '<div class="search_result">';
36
            echo '<span class="mediaref_ref">'.hsc($row).'</span>';
37
            echo '</div>';
38
        }else
39
            $hidden++;
40
    }
41
    if ($hidden){
42
        print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>';
43
    }
44
}
45
46
/**
47
 * Handles the saving of image meta data
48
 *
49
 * @author Andreas Gohr <[email protected]>
50
 * @author Kate Arzamastseva <[email protected]>
51
 *
52
 * @param string $id media id
53
 * @param int $auth permission level
54
 * @param array $data
55
 * @return false|string
56
 */
57
function media_metasave($id,$auth,$data){
58
    if($auth < AUTH_UPLOAD) return false;
59
    if(!checkSecurityToken()) return false;
60
    global $lang;
61
    global $conf;
62
    $src = mediaFN($id);
63
64
    $meta = new JpegMeta($src);
65
    $meta->_parseAll();
66
67
    foreach($data as $key => $val){
68
        $val=trim($val);
69
        if(empty($val)){
70
            $meta->deleteField($key);
71
        }else{
72
            $meta->setField($key,$val);
73
        }
74
    }
75
76
    $old = @filemtime($src);
77
    if(!file_exists(mediaFN($id, $old)) && file_exists($src)) {
78
        // add old revision to the attic
79
        media_saveOldRevision($id);
80
    }
81
    $filesize_old = filesize($src);
82
    if($meta->save()){
83
        if($conf['fperm']) chmod($src, $conf['fperm']);
84
        @clearstatcache(true, $src);
85
        $new = @filemtime($src);
86
        $filesize_new = filesize($src);
87
        $sizechange = $filesize_new - $filesize_old;
88
89
        // add a log entry to the media changelog
90
        addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange);
91
92
        msg($lang['metasaveok'],1);
93
        return $id;
94
    }else{
95
        msg($lang['metasaveerr'],-1);
96
        return false;
97
    }
98
}
99
100
/**
101
 * check if a media is external source
102
 *
103
 * @author Gerrit Uitslag <[email protected]>
104
 *
105
 * @param string $id the media ID or URL
106
 * @return bool
107
 */
108
function media_isexternal($id){
109
    if (preg_match('#^(?:https?|ftp)://#i', $id)) return true;
110
    return false;
111
}
112
113
/**
114
 * Check if a media item is public (eg, external URL or readable by @ALL)
115
 *
116
 * @author Andreas Gohr <[email protected]>
117
 *
118
 * @param string $id  the media ID or URL
119
 * @return bool
120
 */
121
function media_ispublic($id){
122
    if(media_isexternal($id)) return true;
123
    $id = cleanID($id);
124
    if(auth_aclcheck(getNS($id).':*', '', array()) >= AUTH_READ) return true;
125
    return false;
126
}
127
128
/**
129
 * Display the form to edit image meta data
130
 *
131
 * @author Andreas Gohr <[email protected]>
132
 * @author Kate Arzamastseva <[email protected]>
133
 *
134
 * @param string $id media id
135
 * @param int $auth permission level
136
 * @return bool
137
 */
138
function media_metaform($id, $auth) {
139
    global $lang;
140
141
    if ($auth < AUTH_UPLOAD) {
142
        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.DOKU_LF;
143
        return false;
144
    }
145
146
    // load the field descriptions
147
    static $fields = null;
148
    if ($fields === null) {
149
        $config_files = getConfigFiles('mediameta');
150
        foreach ($config_files as $config_file) {
151
            if (file_exists($config_file)) include($config_file);
152
        }
153
    }
154
155
    $src = mediaFN($id);
156
157
    // output
158
    $form = new Form([
159
            'action' => media_managerURL(['tab_details' => 'view'], '&'),
160
            'class' => 'meta'
161
    ]);
162
    $form->addTagOpen('div')->addClass('no');
163
    $form->setHiddenField('img', $id);
164
    $form->setHiddenField('mediado', 'save');
165
    foreach ($fields as $key => $field) {
166
        // get current value
167
        if (empty($field[0])) continue;
168
        $tags = array($field[0]);
169
        if (is_array($field[3])) $tags = array_merge($tags, $field[3]);
170
        $value = tpl_img_getTag($tags, '', $src);
171
        $value = cleanText($value);
172
173
        // prepare attributes
174
        $p = array(
175
            'class' => 'edit',
176
            'id'    => 'meta__'.$key,
177
            'name'  => 'meta['.$field[0].']',
178
        );
179
180
        $form->addTagOpen('div')->addClass('row');
181
        if ($field[2] == 'text') {
182
            $form->addTextInput(
183
                $p['name'],
184
                ($lang[$field[1]] ? $lang[$field[1]] : $field[1] . ':')
185
            )->id($p['id'])->addClass($p['class'])->val($value);
186
        } else {
187
            $form->addTextarea($p['name'], $lang[$field[1]])->id($p['id'])
188
                ->val(formText($value))
189
                ->addClass($p['class'])
190
                ->attr('rows', '6')->attr('cols', '50');
191
        }
192
        $form->addTagClose('div');
193
    }
194
    $form->addTagOpen('div')->addClass('buttons');
195
    $form->addButton('mediado[save]', $lang['btn_save'])->attr('type', 'submit')
196
        ->attrs(['accesskey' => 's']);
197
    $form->addTagClose('div');
198
199
    $form->addTagClose('div');
200
    echo $form->toHTML();
201
    return true;
202
}
203
204
/**
205
 * Convenience function to check if a media file is still in use
206
 *
207
 * @author Michael Klier <[email protected]>
208
 *
209
 * @param string $id media id
210
 * @return array|bool
211
 */
212
function media_inuse($id) {
213
    global $conf;
214
215
    if($conf['refcheck']){
216
        $mediareferences = ft_mediause($id,true);
217
        if(!count($mediareferences)) {
218
            return false;
219
        } else {
220
            return $mediareferences;
221
        }
222
    } else {
223
        return false;
224
    }
225
}
226
227
/**
228
 * Handles media file deletions
229
 *
230
 * If configured, checks for media references before deletion
231
 *
232
 * @author             Andreas Gohr <[email protected]>
233
 *
234
 * @param string $id media id
235
 * @param int $auth no longer used
236
 * @return int One of: 0,
237
 *                     DOKU_MEDIA_DELETED,
238
 *                     DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS,
239
 *                     DOKU_MEDIA_NOT_AUTH,
240
 *                     DOKU_MEDIA_INUSE
241
 */
242
function media_delete($id,$auth){
243
    global $lang;
244
    $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':'));
245
    if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH;
246
    if(media_inuse($id)) return DOKU_MEDIA_INUSE;
247
248
    $file = mediaFN($id);
249
250
    // trigger an event - MEDIA_DELETE_FILE
251
    $data = array();
252
    $data['id']   = $id;
253
    $data['name'] = \dokuwiki\Utf8\PhpString::basename($file);
254
    $data['path'] = $file;
255
    $data['size'] = (file_exists($file)) ? filesize($file) : 0;
256
257
    $data['unl'] = false;
258
    $data['del'] = false;
259
    $evt = new Event('MEDIA_DELETE_FILE',$data);
260
    if ($evt->advise_before()) {
261
        $old = @filemtime($file);
262
        if(!file_exists(mediaFN($id, $old)) && file_exists($file)) {
263
            // add old revision to the attic
264
            media_saveOldRevision($id);
265
        }
266
267
        $data['unl'] = @unlink($file);
268
        if($data['unl']) {
269
            $sizechange = 0 - $data['size'];
270
            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange);
271
272
            $data['del'] = io_sweepNS($id, 'mediadir');
273
        }
274
    }
275
    $evt->advise_after();
276
    unset($evt);
277
278
    if($data['unl'] && $data['del']){
279
        return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS;
280
    }
281
282
    return $data['unl'] ? DOKU_MEDIA_DELETED : 0;
283
}
284
285
/**
286
 * Handle file uploads via XMLHttpRequest
287
 *
288
 * @param string $ns   target namespace
289
 * @param int    $auth current auth check result
290
 * @return false|string false on error, id of the new file on success
291
 */
292
function media_upload_xhr($ns,$auth){
293
    if(!checkSecurityToken()) return false;
294
    global $INPUT;
295
296
    $id = $INPUT->get->str('qqfile');
297
    list($ext,$mime) = mimetype($id);
298
    $input = fopen("php://input", "r");
299
    if (!($tmp = io_mktmpdir())) return false;
300
    $path = $tmp.'/'.md5($id);
301
    $target = fopen($path, "w");
302
    $realSize = stream_copy_to_stream($input, $target);
303
    fclose($target);
304
    fclose($input);
305
    if (isset($_SERVER["CONTENT_LENGTH"]) && ($realSize != (int)$_SERVER["CONTENT_LENGTH"])){
306
        unlink($path);
307
        return false;
308
    }
309
310
    $res = media_save(
311
        array('name' => $path,
312
            'mime' => $mime,
313
            'ext'  => $ext),
314
        $ns.':'.$id,
315
        (($INPUT->get->str('ow') == 'true') ? true : false),
316
        $auth,
317
        'copy'
318
    );
319
    unlink($path);
320
    if ($tmp) io_rmdir($tmp, true);
321
    if (is_array($res)) {
322
        msg($res[0], $res[1]);
323
        return false;
324
    }
325
    return $res;
326
}
327
328
/**
329
 * Handles media file uploads
330
 *
331
 * @author Andreas Gohr <[email protected]>
332
 * @author Michael Klier <[email protected]>
333
 *
334
 * @param string     $ns    target namespace
335
 * @param int        $auth  current auth check result
336
 * @param bool|array $file  $_FILES member, $_FILES['upload'] if false
337
 * @return false|string false on error, id of the new file on success
338
 */
339
function media_upload($ns,$auth,$file=false){
340
    if(!checkSecurityToken()) return false;
341
    global $lang;
342
    global $INPUT;
343
344
    // get file and id
345
    $id   = $INPUT->post->str('mediaid');
346
    if (!$file) $file = $_FILES['upload'];
347
    if(empty($id)) $id = $file['name'];
348
349
    // check for errors (messages are done in lib/exe/mediamanager.php)
350
    if($file['error']) return false;
351
352
    // check extensions
353
    list($fext,$fmime) = mimetype($file['name']);
354
    list($iext,$imime) = mimetype($id);
355
    if($fext && !$iext){
356
        // no extension specified in id - read original one
357
        $id   .= '.'.$fext;
358
        $imime = $fmime;
359
    }elseif($fext && $fext != $iext){
360
        // extension was changed, print warning
361
        msg(sprintf($lang['mediaextchange'],$fext,$iext));
362
    }
363
364
    $res = media_save(array('name' => $file['tmp_name'],
365
                            'mime' => $imime,
366
                            'ext'  => $iext), $ns.':'.$id,
367
                      $INPUT->post->bool('ow'), $auth, 'copy_uploaded_file');
368
    if (is_array($res)) {
369
        msg($res[0], $res[1]);
370
        return false;
371
    }
372
    return $res;
373
}
374
375
/**
376
 * An alternative to move_uploaded_file that copies
377
 *
378
 * Using copy, makes sure any setgid bits on the media directory are honored
379
 *
380
 * @see   move_uploaded_file()
381
 *
382
 * @param string $from
383
 * @param string $to
384
 * @return bool
385
 */
386
function copy_uploaded_file($from, $to){
387
    if(!is_uploaded_file($from)) return false;
388
    $ok = copy($from, $to);
389
    @unlink($from);
390
    return $ok;
391
}
392
393
/**
394
 * This generates an action event and delegates to _media_upload_action().
395
 * Action plugins are allowed to pre/postprocess the uploaded file.
396
 * (The triggered event is preventable.)
397
 *
398
 * Event data:
399
 * $data[0]     fn_tmp:    the temporary file name (read from $_FILES)
400
 * $data[1]     fn:        the file name of the uploaded file
401
 * $data[2]     id:        the future directory id of the uploaded file
402
 * $data[3]     imime:     the mimetype of the uploaded file
403
 * $data[4]     overwrite: if an existing file is going to be overwritten
404
 * $data[5]     move:      name of function that performs move/copy/..
405
 *
406
 * @triggers MEDIA_UPLOAD_FINISH
407
 *
408
 * @param array  $file
409
 * @param string $id   media id
410
 * @param bool   $ow   overwrite?
411
 * @param int    $auth permission level
412
 * @param string $move name of functions that performs move/copy/..
413
 * @return false|array|string
414
 */
415
function media_save($file, $id, $ow, $auth, $move) {
416
    if($auth < AUTH_UPLOAD) {
417
        return array("You don't have permissions to upload files.", -1);
418
    }
419
420
    if (!isset($file['mime']) || !isset($file['ext'])) {
421
        list($ext, $mime) = mimetype($id);
422
        if (!isset($file['mime'])) {
423
            $file['mime'] = $mime;
424
        }
425
        if (!isset($file['ext'])) {
426
            $file['ext'] = $ext;
427
        }
428
    }
429
430
    global $lang, $conf;
431
432
    // get filename
433
    $id   = cleanID($id);
434
    $fn   = mediaFN($id);
435
436
    // get filetype regexp
437
    $types = array_keys(getMimeTypes());
438
    $types = array_map(
439
        function ($q) {
440
            return preg_quote($q, "/");
441
        },
442
        $types
443
    );
444
    $regex = join('|',$types);
445
446
    // because a temp file was created already
447
    if(!preg_match('/\.('.$regex.')$/i',$fn)) {
448
        return array($lang['uploadwrong'],-1);
449
    }
450
451
    //check for overwrite
452
    $overwrite = file_exists($fn);
453
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
454
    if($overwrite && (!$ow || $auth < $auth_ow)) {
455
        return array($lang['uploadexist'], 0);
456
    }
457
    // check for valid content
458
    $ok = media_contentcheck($file['name'], $file['mime']);
459
    if($ok == -1){
460
        return array(sprintf($lang['uploadbadcontent'],'.' . $file['ext']),-1);
461
    }elseif($ok == -2){
462
        return array($lang['uploadspam'],-1);
463
    }elseif($ok == -3){
464
        return array($lang['uploadxss'],-1);
465
    }
466
467
    // prepare event data
468
    $data = array();
469
    $data[0] = $file['name'];
470
    $data[1] = $fn;
471
    $data[2] = $id;
472
    $data[3] = $file['mime'];
473
    $data[4] = $overwrite;
474
    $data[5] = $move;
475
476
    // trigger event
477
    return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true);
478
}
479
480
/**
481
 * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH
482
 *
483
 * @author Michael Klier <[email protected]>
484
 *
485
 * @param array $data event data
486
 * @return false|array|string
487
 */
488
function _media_upload_action($data) {
489
    // fixme do further sanity tests of given data?
490
    if(is_array($data) && count($data)===6) {
491
        return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
492
    } else {
493
        return false; //callback error
494
    }
495
}
496
497
/**
498
 * Saves an uploaded media file
499
 *
500
 * @author Andreas Gohr <[email protected]>
501
 * @author Michael Klier <[email protected]>
502
 * @author Kate Arzamastseva <[email protected]>
503
 *
504
 * @param string $fn_tmp
505
 * @param string $fn
506
 * @param string $id        media id
507
 * @param string $imime     mime type
508
 * @param bool   $overwrite overwrite existing?
509
 * @param string $move      function name
510
 * @return array|string
511
 */
512
function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') {
513
    global $conf;
514
    global $lang;
515
    global $REV;
516
517
    $old = @filemtime($fn);
518
    if(!file_exists(mediaFN($id, $old)) && file_exists($fn)) {
519
        // add old revision to the attic if missing
520
        media_saveOldRevision($id);
521
    }
522
523
    // prepare directory
524
    io_createNamespace($id, 'media');
525
526
    $filesize_old = file_exists($fn) ? filesize($fn) : 0;
527
528
    if($move($fn_tmp, $fn)) {
529
        @clearstatcache(true,$fn);
530
        $new = @filemtime($fn);
531
        // Set the correct permission here.
532
        // Always chmod media because they may be saved with different permissions than expected from the php umask.
533
        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
534
        chmod($fn, $conf['fmode']);
535
        msg($lang['uploadsucc'],1);
536
        media_notify($id,$fn,$imime,$old,$new);
537
        // add a log entry to the media changelog
538
        $filesize_new = filesize($fn);
539
        $sizechange = $filesize_new - $filesize_old;
540
        if($REV) {
541
            addMediaLogEntry(
542
                $new,
543
                $id,
544
                DOKU_CHANGE_TYPE_REVERT,
545
                sprintf($lang['restored'], dformat($REV)),
546
                $REV,
547
                null,
548
                $sizechange
549
            );
550
        } elseif($overwrite) {
551
            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
552
        } else {
553
            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
554
        }
555
        return $id;
556
    }else{
557
        return array($lang['uploadfail'],-1);
558
    }
559
}
560
561
/**
562
 * Moves the current version of media file to the media_attic
563
 * directory
564
 *
565
 * @author Kate Arzamastseva <[email protected]>
566
 *
567
 * @param string $id
568
 * @return int - revision date
569
 */
570
function media_saveOldRevision($id){
571
    global $conf, $lang;
572
573
    $oldf = mediaFN($id);
574
    if(!file_exists($oldf)) return '';
575
    $date = filemtime($oldf);
576
    if (!$conf['mediarevisions']) return $date;
577
578
    $medialog = new MediaChangeLog($id);
579
    if (!$medialog->getRevisionInfo($date)) {
580
        // there was an external edit,
581
        // there is no log entry for current version of file
582
        $sizechange = filesize($oldf);
583
        if(!file_exists(mediaMetaFN($id, '.changes'))) {
584
            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
585
        } else {
586
            $oldRev = $medialog->getRevisions(-1, 1); // from changelog
587
            $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]);
588
            $filesize_old = filesize(mediaFN($id, $oldRev));
589
            $sizechange = $sizechange - $filesize_old;
590
591
            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
592
        }
593
    }
594
595
    $newf = mediaFN($id,$date);
596
    io_makeFileDir($newf);
597
    if(copy($oldf, $newf)) {
598
        // Set the correct permission here.
599
        // Always chmod media because they may be saved with different permissions than expected from the php umask.
600
        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
601
        chmod($newf, $conf['fmode']);
602
    }
603
    return $date;
604
}
605
606
/**
607
 * This function checks if the uploaded content is really what the
608
 * mimetype says it is. We also do spam checking for text types here.
609
 *
610
 * We need to do this stuff because we can not rely on the browser
611
 * to do this check correctly. Yes, IE is broken as usual.
612
 *
613
 * @author Andreas Gohr <[email protected]>
614
 * @link   http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting
615
 * @fixme  check all 26 magic IE filetypes here?
616
 *
617
 * @param string $file path to file
618
 * @param string $mime mimetype
619
 * @return int
620
 */
621
function media_contentcheck($file,$mime){
622
    global $conf;
623
    if($conf['iexssprotect']){
624
        $fh = @fopen($file, 'rb');
625
        if($fh){
626
            $bytes = fread($fh, 256);
627
            fclose($fh);
628
            if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i',$bytes)){
629
                return -3; //XSS: possibly malicious content
630
            }
631
        }
632
    }
633
    if(substr($mime,0,6) == 'image/'){
634
        $info = @getimagesize($file);
635
        if($mime == 'image/gif' && $info[2] != 1){
636
            return -1; // uploaded content did not match the file extension
637
        }elseif($mime == 'image/jpeg' && $info[2] != 2){
638
            return -1;
639
        }elseif($mime == 'image/png' && $info[2] != 3){
640
            return -1;
641
        }
642
        # fixme maybe check other images types as well
643
    }elseif(substr($mime,0,5) == 'text/'){
644
        global $TEXT;
645
        $TEXT = io_readFile($file);
646
        if(checkwordblock()){
647
            return -2; //blocked by the spam blacklist
648
        }
649
    }
650
    return 0;
651
}
652
653
/**
654
 * Send a notify mail on uploads
655
 *
656
 * @author Andreas Gohr <[email protected]>
657
 *
658
 * @param string   $id      media id
659
 * @param string   $file    path to file
660
 * @param string   $mime    mime type
661
 * @param bool|int $old_rev revision timestamp or false
662
 * @return bool
663
 */
664
function media_notify($id,$file,$mime,$old_rev=false,$current_rev=false){
665
    global $conf;
666
    if(empty($conf['notify'])) return false; //notify enabled?
667
668
    $subscription = new MediaSubscriptionSender();
669
    return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev);
670
}
671
672
/**
673
 * List all files in a given Media namespace
674
 *
675
 * @param string      $ns             namespace
676
 * @param null|int    $auth           permission level
677
 * @param string      $jump           id
678
 * @param bool        $fullscreenview
679
 * @param bool|string $sort           sorting order, false skips sorting
680
 */
681
function media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){
0 ignored issues
show
The parameter $jump is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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