Failed Conditions
Push — master ( e8baa3...413313 )
by Andreas
13:09 queued 10:30
created

inc/media.php (2 issues)

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){
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_media',
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
                    media_printfile($item,$auth,$jump);
714
                } else {
715
                    media_printfile_thumbs($item,$auth,$jump);
716
                }
717
            }
718
            if ($fullscreenview) echo '</ul>'.NL;
719
        }
720
    }
721
}
722
723
/**
724
 * Prints tabs for files list actions
725
 *
726
 * @author Kate Arzamastseva <[email protected]>
727
 * @author Adrian Lang <[email protected]>
728
 *
729
 * @param string $selected_tab - opened tab
730
 */
731
732
function media_tabs_files($selected_tab = ''){
733
    global $lang;
734
    $tabs = array();
735
    foreach(array('files'  => 'mediaselect',
736
                  'upload' => 'media_uploadtab',
737
                  'search' => 'media_searchtab') as $tab => $caption) {
738
        $tabs[$tab] = array('href'    => media_managerURL(['tab_files' => $tab], '&'),
739
                            'caption' => $lang[$caption]);
740
    }
741
742
    html_tabs($tabs, $selected_tab);
743
}
744
745
/**
746
 * Prints tabs for files details actions
747
 *
748
 * @author Kate Arzamastseva <[email protected]>
749
 * @param string $image filename of the current image
750
 * @param string $selected_tab opened tab
751
 */
752
function media_tabs_details($image, $selected_tab = '') {
753
    global $lang, $conf;
754
755
    $tabs = array();
756
    $tabs['view'] = array('href'    => media_managerURL(['tab_details' => 'view'], '&'),
757
                          'caption' => $lang['media_viewtab']);
758
759
    list(, $mime) = mimetype($image);
760
    if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) {
761
        $tabs['edit'] = array('href'    => media_managerURL(['tab_details' => 'edit'], '&'),
762
                              'caption' => $lang['media_edittab']);
763
    }
764
    if ($conf['mediarevisions']) {
765
        $tabs['history'] = array('href'    => media_managerURL(['tab_details' => 'history'], '&'),
766
                                 'caption' => $lang['media_historytab']);
767
    }
768
769
    html_tabs($tabs, $selected_tab);
770
}
771
772
/**
773
 * Prints options for the tab that displays a list of all files
774
 *
775
 * @author Kate Arzamastseva <[email protected]>
776
 */
777
function media_tab_files_options() {
778
    global $lang;
779
    global $INPUT;
780
    global $ID;
781
782
    $form = new Form([
783
            'method' => 'get',
784
            'action' => wl($ID),
785
            'class' => 'options'
786
    ]);
787
    $form->addTagOpen('div')->addClass('no');
788
    $form->setHiddenField('sectok', null);
789
    $media_manager_params = media_managerURL([], '', false, true);
790
    foreach ($media_manager_params as $pKey => $pVal) {
791
        $form->setHiddenField($pKey, $pVal);
792
    }
793
    if ($INPUT->has('q')) {
794
        $form->setHiddenField('q', $INPUT->str('q'));
795
    }
796
    $form->addHTML('<ul>'.NL);
797
    foreach (array('list' => array('listType', array('thumbs', 'rows')),
798
                  'sort' => array('sortBy', array('name', 'date')))
799
            as $group => $content) {
800
        $checked = "_media_get_${group}_type";
801
        $checked = $checked();
802
803
        $form->addHTML('<li class="'. $content[0] .'">');
804
        foreach ($content[1] as $option) {
805
            $attrs = array();
806
            if ($checked == $option) {
807
                $attrs['checked'] = 'checked';
808
            }
809
            $radio = $form->addRadioButton(
810
                $group.'_dwmedia',
811
                $lang['media_'.$group.'_'.$option]
812
            )->val($option)->id($content[0].'__'.$option)->addClass($option);
813
            $radio->attrs($attrs);
814
        }
815
        $form->addHTML('</li>'.NL);
816
    }
817
    $form->addHTML('<li>');
818
    $form->addButton('', $lang['btn_apply'])->attr('type', 'submit');
819
    $form->addHTML('</li>'.NL);
820
    $form->addHTML('</ul>'.NL);
821
    $form->addTagClose('div');
822
    print $form->toHTML();
823
}
824
825
/**
826
 * Returns type of sorting for the list of files in media manager
827
 *
828
 * @author Kate Arzamastseva <[email protected]>
829
 *
830
 * @return string - sort type
831
 */
832
function _media_get_sort_type() {
833
    return _media_get_display_param('sort', array('default' => 'name', 'date'));
834
}
835
836
/**
837
 * Returns type of listing for the list of files in media manager
838
 *
839
 * @author Kate Arzamastseva <[email protected]>
840
 *
841
 * @return string - list type
842
 */
843
function _media_get_list_type() {
844
    return _media_get_display_param('list', array('default' => 'thumbs', 'rows'));
845
}
846
847
/**
848
 * Get display parameters
849
 *
850
 * @param string $param   name of parameter
851
 * @param array  $values  allowed values, where default value has index key 'default'
852
 * @return string the parameter value
853
 */
854
function _media_get_display_param($param, $values) {
855
    global $INPUT;
856
    if (in_array($INPUT->str($param), $values)) {
857
        // FIXME: Set cookie
858
        return $INPUT->str($param);
859
    } else {
860
        $val = get_doku_pref($param, $values['default']);
861
        if (!in_array($val, $values)) {
862
            $val = $values['default'];
863
        }
864
        return $val;
865
    }
866
}
867
868
/**
869
 * Prints tab that displays a list of all files
870
 *
871
 * @author Kate Arzamastseva <[email protected]>
872
 *
873
 * @param string    $ns
874
 * @param null|int  $auth permission level
875
 * @param string    $jump item id
876
 */
877
function media_tab_files($ns,$auth=null,$jump='') {
878
    global $lang;
879
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
880
881
    if($auth < AUTH_READ){
882
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
883
    }else{
884
        media_filelist($ns,$auth,$jump,true,_media_get_sort_type());
885
    }
886
}
887
888
/**
889
 * Prints tab that displays uploading form
890
 *
891
 * @author Kate Arzamastseva <[email protected]>
892
 *
893
 * @param string   $ns
894
 * @param null|int $auth permission level
895
 * @param string   $jump item id
896
 */
897
function media_tab_upload($ns,$auth=null,$jump='') {
898
    global $lang;
899
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
900
901
    echo '<div class="upload">'.NL;
902
    if ($auth >= AUTH_UPLOAD) {
903
        echo '<p>' . $lang['mediaupload'] . '</p>';
904
    }
905
    media_uploadform($ns, $auth, true);
906
    echo '</div>'.NL;
907
}
908
909
/**
910
 * Prints tab that displays search form
911
 *
912
 * @author Kate Arzamastseva <[email protected]>
913
 *
914
 * @param string $ns
915
 * @param null|int $auth permission level
916
 */
917
function media_tab_search($ns,$auth=null) {
918
    global $INPUT;
919
920
    $do = $INPUT->str('mediado');
921
    $query = $INPUT->str('q');
922
    echo '<div class="search">'.NL;
923
924
    media_searchform($ns, $query, true);
925
    if ($do == 'searchlist' || $query) {
926
        media_searchlist($query,$ns,$auth,true,_media_get_sort_type());
927
    }
928
    echo '</div>'.NL;
929
}
930
931
/**
932
 * Prints tab that displays mediafile details
933
 *
934
 * @author Kate Arzamastseva <[email protected]>
935
 *
936
 * @param string     $image media id
937
 * @param string     $ns
938
 * @param null|int   $auth  permission level
939
 * @param string|int $rev   revision timestamp or empty string
940
 */
941
function media_tab_view($image, $ns, $auth=null, $rev='') {
942
    global $lang;
943
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
944
945
    if ($image && $auth >= AUTH_READ) {
946
        $meta = new JpegMeta(mediaFN($image, $rev));
947
        media_preview($image, $auth, $rev, $meta);
948
        media_preview_buttons($image, $auth, $rev);
949
        media_details($image, $auth, $rev, $meta);
950
951
    } else {
952
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
953
    }
954
}
955
956
/**
957
 * Prints tab that displays form for editing mediafile metadata
958
 *
959
 * @author Kate Arzamastseva <[email protected]>
960
 *
961
 * @param string     $image media id
962
 * @param string     $ns
963
 * @param null|int   $auth permission level
964
 */
965
function media_tab_edit($image, $ns, $auth=null) {
966
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
967
968
    if ($image) {
969
        list(, $mime) = mimetype($image);
970
        if ($mime == 'image/jpeg') media_metaform($image,$auth);
971
    }
972
}
973
974
/**
975
 * Prints tab that displays mediafile revisions
976
 *
977
 * @author Kate Arzamastseva <[email protected]>
978
 *
979
 * @param string     $image media id
980
 * @param string     $ns
981
 * @param null|int   $auth permission level
982
 */
983
function media_tab_history($image, $ns, $auth=null) {
984
    global $lang;
985
    global $INPUT;
986
987
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
988
    $do = $INPUT->str('mediado');
989
990
    if ($auth >= AUTH_READ && $image) {
991
        if ($do == 'diff'){
992
            media_diff($image, $ns, $auth);
993
        } else {
994
            $first = $INPUT->int('first');
995
            html_revisions($first, $image);
996
        }
997
    } else {
998
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
999
    }
1000
}
1001
1002
/**
1003
 * Prints mediafile details
1004
 *
1005
 * @param string         $image media id
1006
 * @param int            $auth permission level
1007
 * @param int|string     $rev revision timestamp or empty string
1008
 * @param JpegMeta|bool  $meta
1009
 *
1010
 * @author Kate Arzamastseva <[email protected]>
1011
 */
1012
function media_preview($image, $auth, $rev='', $meta=false) {
1013
1014
    $size = media_image_preview_size($image, $rev, $meta);
1015
1016
    if ($size) {
1017
        global $lang;
1018
        echo '<div class="image">';
1019
1020
        $more = array();
1021
        if ($rev) {
1022
            $more['rev'] = $rev;
1023
        } else {
1024
            $t = @filemtime(mediaFN($image));
1025
            $more['t'] = $t;
1026
        }
1027
1028
        $more['w'] = $size[0];
1029
        $more['h'] = $size[1];
1030
        $src = ml($image, $more);
1031
1032
        echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">';
1033
        echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
1034
        echo '</a>';
1035
1036
        echo '</div>'.NL;
1037
    }
1038
}
1039
1040
/**
1041
 * Prints mediafile action buttons
1042
 *
1043
 * @author Kate Arzamastseva <[email protected]>
1044
 *
1045
 * @param string     $image media id
1046
 * @param int        $auth  permission level
1047
 * @param string|int $rev   revision timestamp, or empty string
1048
 */
1049
function media_preview_buttons($image, $auth, $rev = '') {
1050
    global $lang, $conf;
1051
1052
    echo '<ul class="actions">'.DOKU_LF;
1053
1054
    if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) {
1055
1056
        // delete button
1057
        $form = new Form([
1058
            'id' => 'mediamanager__btn_delete',
1059
            'action' => media_managerURL(['delete' => $image], '&'),
1060
        ]);
1061
        $form->addTagOpen('div')->addClass('no');
1062
        $form->addButton('', $lang['btn_delete'])->attr('type', 'submit');
1063
        $form->addTagClose('div');
1064
        echo '<li>';
1065
        echo $form->toHTML();
1066
        echo '</li>'.DOKU_LF;
1067
    }
1068
1069
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1070
    if ($auth >= $auth_ow && !$rev) {
1071
1072
        // upload new version button
1073
        $form = new Form([
1074
            'id' => 'mediamanager__btn_update',
1075
            'action' => media_managerURL(['image' => $image, 'mediado' => 'update'], '&'),
1076
        ]);
1077
        $form->addTagOpen('div')->addClass('no');
1078
        $form->addButton('', $lang['media_update'])->attr('type', 'submit');
1079
        $form->addTagClose('div');
1080
        echo '<li>';
1081
        echo $form->toHTML();
1082
        echo '</li>'.DOKU_LF;
1083
    }
1084
1085
    if ($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))) {
1086
1087
        // restore button
1088
        $form = new Form([
1089
            'id' => 'mediamanager__btn_restore',
1090
            'action'=>media_managerURL(['image' => $image], '&'),
1091
        ]);
1092
        $form->addTagOpen('div')->addClass('no');
1093
        $form->setHiddenField('mediado', 'restore');
1094
        $form->setHiddenField('rev', $rev);
1095
        $form->addButton('', $lang['media_restore'])->attr('type', 'submit');
1096
        $form->addTagClose('div');
1097
        echo '<li>';
1098
        echo $form->toHTML();
1099
        echo '</li>'.DOKU_LF;
1100
    }
1101
1102
    echo '</ul>'.DOKU_LF;
1103
}
1104
1105
/**
1106
 * Returns image width and height for mediamanager preview panel
1107
 *
1108
 * @author Kate Arzamastseva <[email protected]>
1109
 * @param string         $image
1110
 * @param int|string     $rev
1111
 * @param JpegMeta|bool  $meta
1112
 * @param int            $size
1113
 * @return array|false
1114
 */
1115
function media_image_preview_size($image, $rev, $meta, $size = 500) {
1116
    if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false;
1117
1118
    $info = getimagesize(mediaFN($image, $rev));
1119
    $w = (int) $info[0];
1120
    $h = (int) $info[1];
1121
1122
    if($meta && ($w > $size || $h > $size)){
1123
        $ratio = $meta->getResizeRatio($size, $size);
1124
        $w = floor($w * $ratio);
1125
        $h = floor($h * $ratio);
1126
    }
1127
    return array($w, $h);
1128
}
1129
1130
/**
1131
 * Returns the requested EXIF/IPTC tag from the image meta
1132
 *
1133
 * @author Kate Arzamastseva <[email protected]>
1134
 *
1135
 * @param array    $tags array with tags, first existing is returned
1136
 * @param JpegMeta $meta
1137
 * @param string   $alt  alternative value
1138
 * @return string
1139
 */
1140
function media_getTag($tags,$meta,$alt=''){
1141
    if($meta === false) return $alt;
1142
    $info = $meta->getField($tags);
1143
    if($info == false) return $alt;
1144
    return $info;
1145
}
1146
1147
/**
1148
 * Returns mediafile tags
1149
 *
1150
 * @author Kate Arzamastseva <[email protected]>
1151
 *
1152
 * @param JpegMeta $meta
1153
 * @return array list of tags of the mediafile
1154
 */
1155
function media_file_tags($meta) {
1156
    // load the field descriptions
1157
    static $fields = null;
1158
    if(is_null($fields)){
1159
        $config_files = getConfigFiles('mediameta');
1160
        foreach ($config_files as $config_file) {
1161
            if(file_exists($config_file)) include($config_file);
1162
        }
1163
    }
1164
1165
    $tags = array();
1166
1167
    foreach($fields as $key => $tag){
1168
        $t = array();
1169
        if (!empty($tag[0])) $t = array($tag[0]);
1170
        if(isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]);
1171
        $value = media_getTag($t, $meta);
1172
        $tags[] = array('tag' => $tag, 'value' => $value);
1173
    }
1174
1175
    return $tags;
1176
}
1177
1178
/**
1179
 * Prints mediafile tags
1180
 *
1181
 * @author Kate Arzamastseva <[email protected]>
1182
 *
1183
 * @param string        $image image id
1184
 * @param int           $auth  permission level
1185
 * @param string|int    $rev   revision timestamp, or empty string
1186
 * @param bool|JpegMeta $meta  image object, or create one if false
1187
 */
1188
function media_details($image, $auth, $rev='', $meta=false) {
1189
    global $lang;
1190
1191
    if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
1192
    $tags = media_file_tags($meta);
1193
1194
    echo '<dl>'.NL;
1195
    foreach($tags as $tag){
1196
        if ($tag['value']) {
1197
            $value = cleanText($tag['value']);
1198
            echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>';
1199
            if ($tag['tag'][2] == 'date') echo dformat($value);
1200
            else echo hsc($value);
1201
            echo '</dd>'.NL;
1202
        }
1203
    }
1204
    echo '</dl>'.NL;
1205
    echo '<dl>'.NL;
1206
    echo '<dt>'.$lang['reference'].':</dt>';
1207
    $media_usage = ft_mediause($image,true);
1208
    if(count($media_usage) > 0){
1209
        foreach($media_usage as $path){
1210
            echo '<dd>'.html_wikilink($path).'</dd>';
1211
        }
1212
    }else{
1213
        echo '<dd>'.$lang['nothingfound'].'</dd>';
1214
    }
1215
    echo '</dl>'.NL;
1216
1217
}
1218
1219
/**
1220
 * Shows difference between two revisions of file
1221
 *
1222
 * @author Kate Arzamastseva <[email protected]>
1223
 *
1224
 * @param string $image  image id
1225
 * @param string $ns
1226
 * @param int $auth permission level
1227
 * @param bool $fromajax
1228
 * @return false|null|string
1229
 */
1230
function media_diff($image, $ns, $auth, $fromajax = false) {
1231
    global $conf;
1232
    global $INPUT;
1233
1234
    if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return '';
1235
1236
    $rev1 = $INPUT->int('rev');
1237
1238
    $rev2 = $INPUT->ref('rev2');
1239
    if(is_array($rev2)){
1240
        $rev1 = (int) $rev2[0];
1241
        $rev2 = (int) $rev2[1];
1242
1243
        if(!$rev1){
1244
            $rev1 = $rev2;
1245
            unset($rev2);
1246
        }
1247
    }else{
1248
        $rev2 = $INPUT->int('rev2');
1249
    }
1250
1251
    if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false;
1252
    if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false;
1253
1254
    if($rev1 && $rev2){            // two specific revisions wanted
1255
        // make sure order is correct (older on the left)
1256
        if($rev1 < $rev2){
1257
            $l_rev = $rev1;
1258
            $r_rev = $rev2;
1259
        }else{
1260
            $l_rev = $rev2;
1261
            $r_rev = $rev1;
1262
        }
1263
    }elseif($rev1){                // single revision given, compare to current
1264
        $r_rev = '';
1265
        $l_rev = $rev1;
1266
    }else{                        // no revision was given, compare previous to current
1267
        $r_rev = '';
1268
        $medialog = new MediaChangeLog($image);
1269
        $revs = $medialog->getRevisions(0, 1);
1270
        if (file_exists(mediaFN($image, $revs[0]))) {
1271
            $l_rev = $revs[0];
1272
        } else {
1273
            $l_rev = '';
1274
        }
1275
    }
1276
1277
    // prepare event data
1278
    $data = array();
1279
    $data[0] = $image;
1280
    $data[1] = $l_rev;
1281
    $data[2] = $r_rev;
1282
    $data[3] = $ns;
1283
    $data[4] = $auth;
1284
    $data[5] = $fromajax;
1285
1286
    // trigger event
1287
    return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true);
1288
}
1289
1290
/**
1291
 * Callback for media file diff
1292
 *
1293
 * @param array $data event data
1294
 * @return false|null
1295
 */
1296
function _media_file_diff($data) {
1297
    if(is_array($data) && count($data)===6) {
1298
        media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
1299
    } else {
1300
        return false;
1301
    }
1302
}
1303
1304
/**
1305
 * Shows difference between two revisions of image
1306
 *
1307
 * @author Kate Arzamastseva <[email protected]>
1308
 *
1309
 * @param string $image
1310
 * @param string|int $l_rev revision timestamp, or empty string
1311
 * @param string|int $r_rev revision timestamp, or empty string
1312
 * @param string $ns
1313
 * @param int $auth permission level
1314
 * @param bool $fromajax
1315
 */
1316
function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax) {
1317
    global $lang;
1318
    global $INPUT;
1319
1320
    $l_meta = new JpegMeta(mediaFN($image, $l_rev));
1321
    $r_meta = new JpegMeta(mediaFN($image, $r_rev));
1322
1323
    $is_img = preg_match('/\.(jpe?g|gif|png)$/', $image);
1324
    if ($is_img) {
1325
        $l_size = media_image_preview_size($image, $l_rev, $l_meta);
1326
        $r_size = media_image_preview_size($image, $r_rev, $r_meta);
1327
        $is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30));
1328
1329
        $difftype = $INPUT->str('difftype');
1330
1331
        if (!$fromajax) {
1332
            $form = new Form([
1333
                'id' => 'mediamanager__form_diffview',
1334
                'action' => media_managerURL([], '&'),
1335
                'method' => 'get',
1336
                'class' => 'diffView',
1337
            ]);
1338
            $form->addTagOpen('div')->addClass('no');
1339
            $form->setHiddenField('sectok', null);
1340
            $form->setHiddenField('mediado', 'diff');
1341
            $form->setHiddenField('rev2[0]', $l_rev);
1342
            $form->setHiddenField('rev2[1]', $r_rev);
1343
            echo $form->toHTML();
1344
1345
            echo NL.'<div id="mediamanager__diff" >'.NL;
1346
        }
1347
1348
        if ($difftype == 'opacity' || $difftype == 'portions') {
1349
            media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype);
1350
            if (!$fromajax) echo '</div>';
1351
            return;
1352
        }
1353
    }
1354
1355
    list($l_head, $r_head) = (new dokuwiki\Ui\Diff)->diffHead($l_rev, $r_rev, $image, true);
1356
1357
    ?>
1358
    <div class="table">
1359
    <table>
1360
      <tr>
1361
        <th><?php echo $l_head; ?></th>
1362
        <th><?php echo $r_head; ?></th>
1363
      </tr>
1364
    <?php
1365
1366
    echo '<tr class="image">';
1367
    echo '<td>';
1368
    media_preview($image, $auth, $l_rev, $l_meta);
1369
    echo '</td>';
1370
1371
    echo '<td>';
1372
    media_preview($image, $auth, $r_rev, $r_meta);
1373
    echo '</td>';
1374
    echo '</tr>'.NL;
1375
1376
    echo '<tr class="actions">';
1377
    echo '<td>';
1378
    media_preview_buttons($image, $auth, $l_rev);
1379
    echo '</td>';
1380
1381
    echo '<td>';
1382
    media_preview_buttons($image, $auth, $r_rev);
1383
    echo '</td>';
1384
    echo '</tr>'.NL;
1385
1386
    $l_tags = media_file_tags($l_meta);
1387
    $r_tags = media_file_tags($r_meta);
1388
    // FIXME r_tags-only stuff
1389
    foreach ($l_tags as $key => $l_tag) {
1390
        if ($l_tag['value'] != $r_tags[$key]['value']) {
1391
            $r_tags[$key]['highlighted'] = true;
1392
            $l_tags[$key]['highlighted'] = true;
1393
        } else if (!$l_tag['value'] || !$r_tags[$key]['value']) {
1394
            unset($r_tags[$key]);
1395
            unset($l_tags[$key]);
1396
        }
1397
    }
1398
1399
    echo '<tr>';
1400
    foreach(array($l_tags,$r_tags) as $tags){
1401
        echo '<td>'.NL;
1402
1403
        echo '<dl class="img_tags">';
1404
        foreach($tags as $tag){
1405
            $value = cleanText($tag['value']);
1406
            if (!$value) $value = '-';
1407
            echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
1408
            echo '<dd>';
1409
            if ($tag['highlighted']) {
1410
                echo '<strong>';
1411
            }
1412
            if ($tag['tag'][2] == 'date') echo dformat($value);
1413
            else echo hsc($value);
1414
            if ($tag['highlighted']) {
1415
                echo '</strong>';
1416
            }
1417
            echo '</dd>';
1418
        }
1419
        echo '</dl>'.NL;
1420
1421
        echo '</td>';
1422
    }
1423
    echo '</tr>'.NL;
1424
1425
    echo '</table>'.NL;
1426
    echo '</div>'.NL;
1427
1428
    if ($is_img && !$fromajax) echo '</div>';
1429
}
1430
1431
/**
1432
 * Prints two images side by side
1433
 * and slider
1434
 *
1435
 * @author Kate Arzamastseva <[email protected]>
1436
 *
1437
 * @param string $image   image id
1438
 * @param int    $l_rev   revision timestamp, or empty string
1439
 * @param int    $r_rev   revision timestamp, or empty string
1440
 * @param array  $l_size  array with width and height
1441
 * @param array  $r_size  array with width and height
1442
 * @param string $type
1443
 */
1444
function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) {
1445
    if ($l_size != $r_size) {
1446
        if ($r_size[0] > $l_size[0]) {
1447
            $l_size = $r_size;
1448
        }
1449
    }
1450
1451
    $l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1452
    $r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1453
1454
    $l_src = ml($image, $l_more);
1455
    $r_src = ml($image, $r_more);
1456
1457
    // slider
1458
    echo '<div class="slider" style="max-width: '.($l_size[0]-20).'px;" ></div>'.NL;
1459
1460
    // two images in divs
1461
    echo '<div class="imageDiff ' . $type . '">'.NL;
1462
    echo '<div class="image1" style="max-width: '.$l_size[0].'px;">';
1463
    echo '<img src="'.$l_src.'" alt="" />';
1464
    echo '</div>'.NL;
1465
    echo '<div class="image2" style="max-width: '.$l_size[0].'px;">';
1466
    echo '<img src="'.$r_src.'" alt="" />';
1467
    echo '</div>'.NL;
1468
    echo '</div>'.NL;
1469
}
1470
1471
/**
1472
 * Restores an old revision of a media file
1473
 *
1474
 * @param string $image media id
1475
 * @param int    $rev   revision timestamp or empty string
1476
 * @param int    $auth
1477
 * @return string - file's id
1478
 *
1479
 * @author Kate Arzamastseva <[email protected]>
1480
 */
1481
function media_restore($image, $rev, $auth){
1482
    global $conf;
1483
    if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
1484
    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
1485
    if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
1486
    if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
1487
    list(,$imime,) = mimetype($image);
1488
    $res = media_upload_finish(mediaFN($image, $rev),
1489
        mediaFN($image),
1490
        $image,
1491
        $imime,
1492
        true,
1493
        'copy');
1494
    if (is_array($res)) {
1495
        msg($res[0], $res[1]);
1496
        return false;
1497
    }
1498
    return $res;
1499
}
1500
1501
/**
1502
 * List all files found by the search request
1503
 *
1504
 * @author Tobias Sarnowski <[email protected]>
1505
 * @author Andreas Gohr <[email protected]>
1506
 * @author Kate Arzamastseva <[email protected]>
1507
 * @triggers MEDIA_SEARCH
1508
 *
1509
 * @param string $query
1510
 * @param string $ns
1511
 * @param null|int $auth
1512
 * @param bool $fullscreen
1513
 * @param string $sort
1514
 */
1515
function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort='natural'){
1516
    global $conf;
1517
    global $lang;
1518
1519
    $ns = cleanID($ns);
1520
    $evdata = array(
1521
        'ns'    => $ns,
1522
        'data'  => array(),
1523
        'query' => $query
1524
    );
1525
    if (!blank($query)) {
1526
        $evt = new Event('MEDIA_SEARCH', $evdata);
1527
        if ($evt->advise_before()) {
1528
            $dir = utf8_encodeFN(str_replace(':','/',$evdata['ns']));
1529
            $quoted = preg_quote($evdata['query'],'/');
1530
            //apply globbing
1531
            $quoted = str_replace(array('\*', '\?'), array('.*', '.'), $quoted, $count);
1532
1533
            //if we use globbing file name must match entirely but may be preceded by arbitrary namespace
1534
            if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$';
1535
1536
            $pattern = '/'.$quoted.'/i';
1537
            search($evdata['data'],
1538
                    $conf['mediadir'],
1539
                    'search_media',
1540
                    array('showmsg'=>false,'pattern'=>$pattern),
1541
                    $dir,
1542
                    1,
1543
                    $sort);
1544
        }
1545
        $evt->advise_after();
1546
        unset($evt);
1547
    }
1548
1549
    if (!$fullscreen) {
1550
        echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL;
1551
        media_searchform($ns,$query);
1552
    }
1553
1554
    if(!count($evdata['data'])){
1555
        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
1556
    }else {
1557
        if ($fullscreen) {
1558
            echo '<ul class="' . _media_get_list_type() . '">';
1559
        }
1560
        foreach($evdata['data'] as $item){
1561
            if (!$fullscreen) media_printfile($item,$item['perm'],'',true);
1562
            else media_printfile_thumbs($item,$item['perm'],false,true);
1563
        }
1564
        if ($fullscreen) echo '</ul>'.NL;
1565
    }
1566
}
1567
1568
/**
1569
 * Formats and prints one file in the list
1570
 *
1571
 * @param array     $item
1572
 * @param int       $auth              permission level
1573
 * @param string    $jump              item id
1574
 * @param bool      $display_namespace
1575
 */
1576
function media_printfile($item,$auth,$jump,$display_namespace=false){
1577
    global $lang;
1578
1579
    // Prepare zebra coloring
1580
    // I always wanted to use this variable name :-D
1581
    static $twibble = 1;
1582
    $twibble *= -1;
1583
    $zebra = ($twibble == -1) ? 'odd' : 'even';
1584
1585
    // Automatically jump to recent action
1586
    if($jump == $item['id']) {
1587
        $jump = ' id="scroll__here" ';
1588
    }else{
1589
        $jump = '';
1590
    }
1591
1592
    // Prepare fileicons
1593
    list($ext) = mimetype($item['file'],false);
1594
    $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
1595
    $class = 'select mediafile mf_'.$class;
1596
1597
    // Prepare filename
1598
    $file = utf8_decodeFN($item['file']);
1599
1600
    // Prepare info
1601
    $info = '';
1602
    if($item['isimg']){
1603
        $info .= (int) $item['meta']->getField('File.Width');
1604
        $info .= '&#215;';
1605
        $info .= (int) $item['meta']->getField('File.Height');
1606
        $info .= ' ';
1607
    }
1608
    $info .= '<i>'.dformat($item['mtime']).'</i>';
1609
    $info .= ' ';
1610
    $info .= filesize_h($item['size']);
1611
1612
    // output
1613
    echo '<div class="'.$zebra.'"'.$jump.' title="'.hsc($item['id']).'">'.NL;
1614
    if (!$display_namespace) {
1615
        echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($file).'</a> ';
1616
    } else {
1617
        echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($item['id']).'</a><br/>';
1618
    }
1619
    echo '<span class="info">('.$info.')</span>'.NL;
1620
1621
    // view button
1622
    $link = ml($item['id'],'',true);
1623
    echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '.
1624
        'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>';
1625
1626
    // mediamanager button
1627
    $link = wl('',array('do'=>'media','image'=>$item['id'],'ns'=>getNS($item['id'])));
1628
    echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/mediamanager.png" '.
1629
        'alt="'.$lang['btn_media'].'" title="'.$lang['btn_media'].'" class="btn" /></a>';
1630
1631
    // delete button
1632
    if($item['writable'] && $auth >= AUTH_DELETE){
1633
        $link = DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($item['id']).
1634
            '&amp;sectok='.getSecurityToken();
1635
        echo ' <a href="'.$link.'" class="btn_media_delete" title="'.$item['id'].'">'.
1636
            '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '.
1637
            'title="'.$lang['btn_delete'].'" class="btn" /></a>';
1638
    }
1639
1640
    echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">';
1641
    echo $lang['mediausage'].' <code>{{:'.$item['id'].'}}</code>';
1642
    echo '</div>';
1643
    if($item['isimg']) media_printimgdetail($item);
1644
    echo '<div class="clearer"></div>'.NL;
1645
    echo '</div>'.NL;
1646
}
1647
1648
/**
1649
 * Display a media icon
1650
 *
1651
 * @param string $filename media id
1652
 * @param string $size     the size subfolder, if not specified 16x16 is used
1653
 * @return string html
1654
 */
1655
function media_printicon($filename, $size=''){
1656
    list($ext) = mimetype(mediaFN($filename),false);
1657
1658
    if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) {
1659
        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png';
1660
    } else {
1661
        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png';
1662
    }
1663
1664
    return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
1665
}
1666
1667
/**
1668
 * Formats and prints one file in the list in the thumbnails view
1669
 *
1670
 * @author Kate Arzamastseva <[email protected]>
1671
 *
1672
 * @param array       $item
1673
 * @param int         $auth              permission level
1674
 * @param bool|string $jump              item id
1675
 * @param bool        $display_namespace
1676
 */
1677
function media_printfile_thumbs($item,$auth,$jump=false,$display_namespace=false){
1678
1679
    // Prepare filename
1680
    $file = utf8_decodeFN($item['file']);
1681
1682
    // output
1683
    echo '<li><dl title="'.hsc($item['id']).'">'.NL;
1684
1685
        echo '<dt>';
1686
    if($item['isimg']) {
1687
        media_printimgdetail($item, true);
1688
1689
    } else {
1690
        echo '<a id="d_:'.$item['id'].'" class="image" title="'.$item['id'].'" href="'.
1691
            media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']),
1692
            'tab_details' => 'view']).'">';
1693
        echo media_printicon($item['id'], '32x32');
1694
        echo '</a>';
1695
    }
1696
    echo '</dt>'.NL;
1697
    if (!$display_namespace) {
1698
        $name = hsc($file);
1699
    } else {
1700
        $name = hsc($item['id']);
1701
    }
1702
    echo '<dd class="name"><a href="'.media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']),
1703
        'tab_details' => 'view']).'" id="h_:'.$item['id'].'">'.$name.'</a></dd>'.NL;
1704
1705
    if($item['isimg']){
1706
        $size = '';
1707
        $size .= (int) $item['meta']->getField('File.Width');
1708
        $size .= '&#215;';
1709
        $size .= (int) $item['meta']->getField('File.Height');
1710
        echo '<dd class="size">'.$size.'</dd>'.NL;
1711
    } else {
1712
        echo '<dd class="size">&#160;</dd>'.NL;
1713
    }
1714
    $date = dformat($item['mtime']);
1715
    echo '<dd class="date">'.$date.'</dd>'.NL;
1716
    $filesize = filesize_h($item['size']);
1717
    echo '<dd class="filesize">'.$filesize.'</dd>'.NL;
1718
    echo '</dl></li>'.NL;
1719
}
1720
1721
/**
1722
 * Prints a thumbnail and metainfo
1723
 *
1724
 * @param array $item
1725
 * @param bool  $fullscreen
1726
 */
1727
function media_printimgdetail($item, $fullscreen=false){
1728
    // prepare thumbnail
1729
    $size = $fullscreen ? 90 : 120;
1730
1731
    $w = (int) $item['meta']->getField('File.Width');
1732
    $h = (int) $item['meta']->getField('File.Height');
1733
    if($w>$size || $h>$size){
1734
        if (!$fullscreen) {
1735
            $ratio = $item['meta']->getResizeRatio($size);
1736
        } else {
1737
            $ratio = $item['meta']->getResizeRatio($size,$size);
1738
        }
1739
        $w = floor($w * $ratio);
1740
        $h = floor($h * $ratio);
1741
    }
1742
    $src = ml($item['id'],array('w'=>$w,'h'=>$h,'t'=>$item['mtime']));
1743
    $p = array();
1744
    if (!$fullscreen) {
1745
        // In fullscreen mediamanager view, image resizing is done via CSS.
1746
        $p['width']  = $w;
1747
        $p['height'] = $h;
1748
    }
1749
    $p['alt']    = $item['id'];
1750
    $att = buildAttributes($p);
1751
1752
    // output
1753
    if ($fullscreen) {
1754
        echo '<a id="l_:'.$item['id'].'" class="image thumb" href="'.
1755
            media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view']).'">';
1756
        echo '<img src="'.$src.'" '.$att.' />';
1757
        echo '</a>';
1758
    }
1759
1760
    if ($fullscreen) return;
1761
1762
    echo '<div class="detail">';
1763
    echo '<div class="thumb">';
1764
    echo '<a id="d_:'.$item['id'].'" class="select">';
1765
    echo '<img src="'.$src.'" '.$att.' />';
1766
    echo '</a>';
1767
    echo '</div>';
1768
1769
    // read EXIF/IPTC data
1770
    $t = $item['meta']->getField(array('IPTC.Headline','xmp.dc:title'));
1771
    $d = $item['meta']->getField(array('IPTC.Caption','EXIF.UserComment',
1772
                'EXIF.TIFFImageDescription',
1773
                'EXIF.TIFFUserComment'));
1774
    if(\dokuwiki\Utf8\PhpString::strlen($d) > 250) $d = \dokuwiki\Utf8\PhpString::substr($d,0,250).'...';
1775
    $k = $item['meta']->getField(array('IPTC.Keywords','IPTC.Category','xmp.dc:subject'));
1776
1777
    // print EXIF/IPTC data
1778
    if($t || $d || $k ){
1779
        echo '<p>';
1780
        if($t) echo '<strong>'.hsc($t).'</strong><br />';
1781
        if($d) echo hsc($d).'<br />';
1782
        if($t) echo '<em>'.hsc($k).'</em>';
1783
        echo '</p>';
1784
    }
1785
    echo '</div>';
1786
}
1787
1788
/**
1789
 * Build link based on the current, adding/rewriting parameters
1790
 *
1791
 * @author Kate Arzamastseva <[email protected]>
1792
 *
1793
 * @param array|bool $params
1794
 * @param string     $amp           separator
1795
 * @param bool       $abs           absolute url?
1796
 * @param bool       $params_array  return the parmeters array?
1797
 * @return string|array - link or link parameters
1798
 */
1799
function media_managerURL($params = false, $amp = '&amp;', $abs = false, $params_array = false) {
1800
    global $ID;
1801
    global $INPUT;
1802
1803
    $gets = array('do' => 'media');
1804
    $media_manager_params = array('tab_files', 'tab_details', 'image', 'ns', 'list', 'sort');
1805
    foreach ($media_manager_params as $x) {
1806
        if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x);
1807
    }
1808
1809
    if ($params) {
1810
        $gets = $params + $gets;
1811
    }
1812
    unset($gets['id']);
1813
    if (isset($gets['delete'])) {
1814
        unset($gets['image']);
1815
        unset($gets['tab_details']);
1816
    }
1817
1818
    if ($params_array) return $gets;
1819
1820
    return wl($ID,$gets,$abs,$amp);
1821
}
1822
1823
/**
1824
 * Print the media upload form if permissions are correct
1825
 *
1826
 * @author Andreas Gohr <[email protected]>
1827
 * @author Kate Arzamastseva <[email protected]>
1828
 *
1829
 * @param string $ns
1830
 * @param int    $auth permission level
1831
 * @param bool  $fullscreen
1832
 */
1833
function media_uploadform($ns, $auth, $fullscreen = false) {
1834
    global $lang;
1835
    global $conf;
1836
    global $INPUT;
1837
1838
    if ($auth < AUTH_UPLOAD) {
1839
        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
1840
        return;
1841
    }
1842
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1843
1844
    $update = false;
1845
    $id = '';
1846
    if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') {
1847
        $update = true;
1848
        $id = cleanID($INPUT->str('image'));
1849
    }
1850
1851
    // The default HTML upload form
1852
    $form = new Form([
1853
        'id' => 'dw__upload',
1854
        'enctype' => 'multipart/form-data',
1855
        'action' => ($fullscreen)
1856
                    ? media_managerURL(['tab_files' => 'files', 'tab_details' => 'view'], '&')
1857
                    : DOKU_BASE.'lib/exe/mediamanager.php',
1858
    ]);
1859
    $form->addTagOpen('div')->addClass('no');
1860
    $form->setHiddenField('ns', hsc($ns));  // FIXME hsc required?
1861
    $form->addTagOpen('p');
1862
    $form->addTextInput('upload', $lang['txt_upload'])->id('upload__file')
1863
            ->attrs(['type' => 'file']);
1864
    $form->addTagClose('p');
1865
    $form->addTagOpen('p');
1866
    $form->addTextInput('mediaid', $lang['txt_filename'])->id('upload__name')
1867
            ->val(noNS($id));
1868
    $form->addButton('', $lang['btn_upload'])->attr('type', 'submit');
1869
    $form->addTagClose('p');
1870
    if ($auth >= $auth_ow){
1871
        $form->addTagOpen('p');
1872
        $attrs = array();
1873
        if ($update) $attrs['checked'] = 'checked';
1874
        $form->addCheckbox('ow', $lang['txt_overwrt'])->id('dw__ow')->val('1')
1875
            ->addClass('check')->attrs($attrs);
1876
        $form->addTagClose('p');
1877
    }
1878
    $form->addTagClose('div');
1879
1880
    if (!$fullscreen) {
1881
        echo '<div class="upload">'. $lang['mediaupload'] .'</div>'.DOKU_LF;
1882
    } else {
1883
        echo DOKU_LF;
1884
    }
1885
1886
    echo '<div id="mediamanager__uploader">'.DOKU_LF;
1887
    echo $form->toHTML('Upload');
1888
    echo '</div>'.DOKU_LF;
1889
1890
    echo '<p class="maxsize">';
1891
    printf($lang['maxuploadsize'], filesize_h(media_getuploadsize()));
1892
    echo ' <a class="allowedmime" href="#">'. $lang['allowedmime'] .'</a>';
1893
    echo ' <span>'. implode(', ', array_keys(getMimeTypes())) .'</span>';
1894
    echo '</p>'.DOKU_LF;
1895
}
1896
1897
/**
1898
 * Returns the size uploaded files may have
1899
 *
1900
 * This uses a conservative approach using the lowest number found
1901
 * in any of the limiting ini settings
1902
 *
1903
 * @returns int size in bytes
1904
 */
1905
function media_getuploadsize(){
1906
    $okay = 0;
1907
1908
    $post = (int) php_to_byte(@ini_get('post_max_size'));
1909
    $suho = (int) php_to_byte(@ini_get('suhosin.post.max_value_length'));
1910
    $upld = (int) php_to_byte(@ini_get('upload_max_filesize'));
1911
1912
    if($post && ($post < $okay || $okay == 0)) $okay = $post;
1913
    if($suho && ($suho < $okay || $okay == 0)) $okay = $suho;
1914
    if($upld && ($upld < $okay || $okay == 0)) $okay = $upld;
1915
1916
    return $okay;
1917
}
1918
1919
/**
1920
 * Print the search field form
1921
 *
1922
 * @author Tobias Sarnowski <[email protected]>
1923
 * @author Kate Arzamastseva <[email protected]>
1924
 *
1925
 * @param string $ns
1926
 * @param string $query
1927
 * @param bool $fullscreen
1928
 */
1929
function media_searchform($ns, $query = '', $fullscreen = false) {
1930
    global $lang;
1931
1932
    // The default HTML search form
1933
    $form = new Form([
1934
        'id'     => 'dw__mediasearch',
1935
        'action' => ($fullscreen)
1936
                    ? media_managerURL([], '&')
1937
                    : DOKU_BASE.'lib/exe/mediamanager.php',
1938
    ]);
1939
    $form->addTagOpen('div')->addClass('no');
1940
    $form->setHiddenField('ns', $ns);
1941
    $form->setHiddenField($fullscreen ? 'mediado' : 'do', 'searchlist');
1942
1943
    $form->addTagOpen('p');
1944
    $form->addTextInput('q', $lang['searchmedia'])
1945
            ->attr('title', sprintf($lang['searchmedia_in'], hsc($ns) .':*'))
1946
            ->val($query);
1947
    $form->addHTML(' ');
1948
    $form->addButton('', $lang['btn_search'])->attr('type', 'submit');
1949
    $form->addTagClose('p');
1950
    $form->addTagClose('div');
1951
    print $form->toHTML('SearchMedia');
1952
}
1953
1954
/**
1955
 * Build a tree outline of available media namespaces
1956
 *
1957
 * @author Andreas Gohr <[email protected]>
1958
 *
1959
 * @param string $ns
1960
 */
1961
function media_nstree($ns){
1962
    global $conf;
1963
    global $lang;
1964
1965
    // currently selected namespace
1966
    $ns  = cleanID($ns);
1967
    if(empty($ns)){
1968
        global $ID;
1969
        $ns = (string)getNS($ID);
1970
    }
1971
1972
    $ns_dir  = utf8_encodeFN(str_replace(':','/',$ns));
1973
1974
    $data = array();
1975
    search($data,$conf['mediadir'],'search_index',array('ns' => $ns_dir, 'nofiles' => true));
1976
1977
    // wrap a list with the root level around the other namespaces
1978
    array_unshift($data, array('level' => 0, 'id' => '', 'open' =>'true',
1979
                               'label' => '['.$lang['mediaroot'].']'));
1980
1981
    // insert the current ns into the hierarchy if it isn't already part of it
1982
    $ns_parts = explode(':', $ns);
1983
    $tmp_ns = '';
1984
    $pos = 0;
1985
    foreach ($ns_parts as $level => $part) {
1986
        if ($tmp_ns) $tmp_ns .= ':'.$part;
1987
        else $tmp_ns = $part;
1988
1989
        // find the namespace parts or insert them
1990
        while ($data[$pos]['id'] != $tmp_ns) {
1991
            if (
1992
                $pos >= count($data) ||
1993
                ($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
1994
            ) {
1995
                array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true')));
1996
                break;
1997
            }
1998
            ++$pos;
1999
        }
2000
    }
2001
2002
    echo html_buildlist($data,'idx','media_nstree_item','media_nstree_li');
2003
}
2004
2005
/**
2006
 * Userfunction for html_buildlist
2007
 *
2008
 * Prints a media namespace tree item
2009
 *
2010
 * @author Andreas Gohr <[email protected]>
2011
 *
2012
 * @param array $item
2013
 * @return string html
2014
 */
2015
function media_nstree_item($item){
2016
    global $INPUT;
2017
    $pos   = strrpos($item['id'], ':');
2018
    $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0);
2019
    if(empty($item['label'])) $item['label'] = $label;
2020
2021
    $ret  = '';
2022
    if (!($INPUT->str('do') == 'media'))
2023
    $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
2024
    else $ret .= '<a href="'.media_managerURL(['ns' => idfilter($item['id'], false), 'tab_files' => 'files'])
2025
        .'" class="idx_dir">';
2026
    $ret .= $item['label'];
2027
    $ret .= '</a>';
2028
    return $ret;
2029
}
2030
2031
/**
2032
 * Userfunction for html_buildlist
2033
 *
2034
 * Prints a media namespace tree item opener
2035
 *
2036
 * @author Andreas Gohr <[email protected]>
2037
 *
2038
 * @param array $item
2039
 * @return string html
2040
 */
2041
function media_nstree_li($item){
2042
    $class='media level'.$item['level'];
2043
    if($item['open']){
2044
        $class .= ' open';
2045
        $img   = DOKU_BASE.'lib/images/minus.gif';
2046
        $alt   = '−';
2047
    }else{
2048
        $class .= ' closed';
2049
        $img   = DOKU_BASE.'lib/images/plus.gif';
2050
        $alt   = '+';
2051
    }
2052
    // TODO: only deliver an image if it actually has a subtree...
2053
    return '<li class="'.$class.'">'.
2054
        '<img src="'.$img.'" alt="'.$alt.'" />';
2055
}
2056
2057
/**
2058
 * Resizes the given image to the given size
2059
 *
2060
 * @author  Andreas Gohr <[email protected]>
2061
 *
2062
 * @param string $file filename, path to file
2063
 * @param string $ext  extension
2064
 * @param int    $w    desired width
2065
 * @param int    $h    desired height
2066
 * @return string path to resized or original size if failed
2067
 */
2068
function media_resize_image($file, $ext, $w, $h=0){
2069
    global $conf;
2070
    if(!$h) $h = $w;
2071
    // we wont scale up to infinity
2072
    if($w > 2000 || $h > 2000) return $file;
2073
2074
    //cache
2075
    $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
2076
    $mtime = (int) @filemtime($local); // 0 if not exists
2077
2078
    $options = [
2079
        'quality' => $conf['jpg_quality'],
2080
        'imconvert' => $conf['im_convert'],
2081
    ];
2082
2083
    if( $mtime <= (int) @filemtime($file) ) {
2084
        try {
2085
            \splitbrain\slika\Slika::run($file, $options)
2086
                                   ->autorotate()
2087
                                   ->resize($w, $h)
2088
                                   ->save($local, $ext);
2089
            if($conf['fperm']) @chmod($local, $conf['fperm']);
2090
        } catch (\splitbrain\slika\Exception $e) {
2091
            dbglog($e->getMessage());
0 ignored issues
show
Deprecated Code introduced by
The function dbglog() has been deprecated with message: 2020-08-13

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2092
            return $file;
2093
        }
2094
    }
2095
2096
    return $local;
2097
}
2098
2099
/**
2100
 * Center crops the given image to the wanted size
2101
 *
2102
 * @author  Andreas Gohr <[email protected]>
2103
 *
2104
 * @param string $file filename, path to file
2105
 * @param string $ext  extension
2106
 * @param int    $w    desired width
2107
 * @param int    $h    desired height
2108
 * @return string path to resized or original size if failed
2109
 */
2110
function media_crop_image($file, $ext, $w, $h=0){
2111
    global $conf;
2112
    if(!$h) $h = $w;
2113
    // we wont scale up to infinity
2114
    if($w > 2000 || $h > 2000) return $file;
2115
2116
    //cache
2117
    $local = getCacheName($file,'.media.'.$w.'x'.$h.'.crop.'.$ext);
2118
    $mtime = (int) @filemtime($local); // 0 if not exists
2119
2120
    $options = [
2121
        'quality' => $conf['jpg_quality'],
2122
        'imconvert' => $conf['im_convert'],
2123
    ];
2124
2125
    if( $mtime <= (int) @filemtime($file) ) {
2126
        try {
2127
            \splitbrain\slika\Slika::run($file, $options)
2128
                                   ->autorotate()
2129
                                    ->crop($w, $h)
2130
                                    ->save($local, $ext);
2131
            if($conf['fperm']) @chmod($local, $conf['fperm']);
2132
        } catch (\splitbrain\slika\Exception $e) {
2133
            dbglog($e->getMessage());
0 ignored issues
show
Deprecated Code introduced by
The function dbglog() has been deprecated with message: 2020-08-13

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2134
            return $file;
2135
        }
2136
    }
2137
2138
    return $local;
2139
}
2140
2141
/**
2142
 * Calculate a token to be used to verify fetch requests for resized or
2143
 * cropped images have been internally generated - and prevent external
2144
 * DDOS attacks via fetch
2145
 *
2146
 * @author Christopher Smith <[email protected]>
2147
 *
2148
 * @param string  $id    id of the image
2149
 * @param int     $w     resize/crop width
2150
 * @param int     $h     resize/crop height
2151
 * @return string token or empty string if no token required
2152
 */
2153
function media_get_token($id,$w,$h){
2154
    // token is only required for modified images
2155
    if ($w || $h || media_isexternal($id)) {
2156
        $token = $id;
2157
        if ($w) $token .= '.'.$w;
2158
        if ($h) $token .= '.'.$h;
2159
2160
        return substr(\dokuwiki\PassHash::hmac('md5', $token, auth_cookiesalt()),0,6);
2161
    }
2162
2163
    return '';
2164
}
2165
2166
/**
2167
 * Download a remote file and return local filename
2168
 *
2169
 * returns false if download fails. Uses cached file if available and
2170
 * wanted
2171
 *
2172
 * @author  Andreas Gohr <[email protected]>
2173
 * @author  Pavel Vitis <[email protected]>
2174
 *
2175
 * @param string $url
2176
 * @param string $ext   extension
2177
 * @param int    $cache cachetime in seconds
2178
 * @return false|string path to cached file
2179
 */
2180
function media_get_from_URL($url,$ext,$cache){
2181
    global $conf;
2182
2183
    // if no cache or fetchsize just redirect
2184
    if ($cache==0)           return false;
2185
    if (!$conf['fetchsize']) return false;
2186
2187
    $local = getCacheName(strtolower($url),".media.$ext");
2188
    $mtime = @filemtime($local); // 0 if not exists
2189
2190
    //decide if download needed:
2191
    if(($mtime == 0) || // cache does not exist
2192
        ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired
2193
    ) {
2194
        if(media_image_download($url, $local)) {
2195
            return $local;
2196
        } else {
2197
            return false;
2198
        }
2199
    }
2200
2201
    //if cache exists use it else
2202
    if($mtime) return $local;
2203
2204
    //else return false
2205
    return false;
2206
}
2207
2208
/**
2209
 * Download image files
2210
 *
2211
 * @author Andreas Gohr <[email protected]>
2212
 *
2213
 * @param string $url
2214
 * @param string $file path to file in which to put the downloaded content
2215
 * @return bool
2216
 */
2217
function media_image_download($url,$file){
2218
    global $conf;
2219
    $http = new DokuHTTPClient();
2220
    $http->keep_alive = false; // we do single ops here, no need for keep-alive
2221
2222
    $http->max_bodysize = $conf['fetchsize'];
2223
    $http->timeout = 25; //max. 25 sec
2224
    $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
2225
2226
    $data = $http->get($url);
2227
    if(!$data) return false;
2228
2229
    $fileexists = file_exists($file);
2230
    $fp = @fopen($file,"w");
2231
    if(!$fp) return false;
2232
    fwrite($fp,$data);
2233
    fclose($fp);
2234
    if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
2235
2236
    // check if it is really an image
2237
    $info = @getimagesize($file);
2238
    if(!$info){
2239
        @unlink($file);
2240
        return false;
2241
    }
2242
2243
    return true;
2244
}
2245
2246
/**
2247
 * resize images using external ImageMagick convert program
2248
 *
2249
 * @author Pavel Vitis <[email protected]>
2250
 * @author Andreas Gohr <[email protected]>
2251
 *
2252
 * @param string $ext     extension
2253
 * @param string $from    filename path to file
2254
 * @param int    $from_w  original width
2255
 * @param int    $from_h  original height
2256
 * @param string $to      path to resized file
2257
 * @param int    $to_w    desired width
2258
 * @param int    $to_h    desired height
2259
 * @return bool
2260
 */
2261
function media_resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
2262
    global $conf;
2263
2264
    // check if convert is configured
2265
    if(!$conf['im_convert']) return false;
2266
2267
    // prepare command
2268
    $cmd  = $conf['im_convert'];
2269
    $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
2270
    if ($ext == 'jpg' || $ext == 'jpeg') {
2271
        $cmd .= ' -quality '.$conf['jpg_quality'];
2272
    }
2273
    $cmd .= " $from $to";
2274
2275
    @exec($cmd,$out,$retval);
2276
    if ($retval == 0) return true;
2277
    return false;
2278
}
2279
2280
/**
2281
 * crop images using external ImageMagick convert program
2282
 *
2283
 * @author Andreas Gohr <[email protected]>
2284
 *
2285
 * @param string $ext     extension
2286
 * @param string $from    filename path to file
2287
 * @param int    $from_w  original width
2288
 * @param int    $from_h  original height
2289
 * @param string $to      path to resized file
2290
 * @param int    $to_w    desired width
2291
 * @param int    $to_h    desired height
2292
 * @param int    $ofs_x   offset of crop centre
2293
 * @param int    $ofs_y   offset of crop centre
2294
 * @return bool
2295
 * @deprecated 2020-09-01
2296
 */
2297
function media_crop_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x,$ofs_y){
2298
    global $conf;
2299
    dbg_deprecated('splitbrain\\Slika');
2300
2301
    // check if convert is configured
2302
    if(!$conf['im_convert']) return false;
2303
2304
    // prepare command
2305
    $cmd  = $conf['im_convert'];
2306
    $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y;
2307
    if ($ext == 'jpg' || $ext == 'jpeg') {
2308
        $cmd .= ' -quality '.$conf['jpg_quality'];
2309
    }
2310
    $cmd .= " $from $to";
2311
2312
    @exec($cmd,$out,$retval);
2313
    if ($retval == 0) return true;
2314
    return false;
2315
}
2316
2317
/**
2318
 * resize or crop images using PHP's libGD support
2319
 *
2320
 * @author Andreas Gohr <[email protected]>
2321
 * @author Sebastian Wienecke <[email protected]>
2322
 *
2323
 * @param string $ext     extension
2324
 * @param string $from    filename path to file
2325
 * @param int    $from_w  original width
2326
 * @param int    $from_h  original height
2327
 * @param string $to      path to resized file
2328
 * @param int    $to_w    desired width
2329
 * @param int    $to_h    desired height
2330
 * @param int    $ofs_x   offset of crop centre
2331
 * @param int    $ofs_y   offset of crop centre
2332
 * @return bool
2333
 * @deprecated 2020-09-01
2334
 */
2335
function media_resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x=0,$ofs_y=0){
2336
    global $conf;
2337
    dbg_deprecated('splitbrain\\Slika');
2338
2339
    if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
2340
2341
    // check available memory
2342
    if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
2343
        return false;
2344
    }
2345
2346
    // create an image of the given filetype
2347
    $image = false;
2348
    if ($ext == 'jpg' || $ext == 'jpeg'){
2349
        if(!function_exists("imagecreatefromjpeg")) return false;
2350
        $image = @imagecreatefromjpeg($from);
2351
    }elseif($ext == 'png') {
2352
        if(!function_exists("imagecreatefrompng")) return false;
2353
        $image = @imagecreatefrompng($from);
2354
2355
    }elseif($ext == 'gif') {
2356
        if(!function_exists("imagecreatefromgif")) return false;
2357
        $image = @imagecreatefromgif($from);
2358
    }
2359
    if(!$image) return false;
2360
2361
    $newimg = false;
2362
    if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){
2363
        $newimg = @imagecreatetruecolor ($to_w, $to_h);
2364
    }
2365
    if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
2366
    if(!$newimg){
2367
        imagedestroy($image);
2368
        return false;
2369
    }
2370
2371
    //keep png alpha channel if possible
2372
    if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
2373
        imagealphablending($newimg, false);
2374
        imagesavealpha($newimg,true);
2375
    }
2376
2377
    //keep gif transparent color if possible
2378
    if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
2379
        if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
2380
            $transcolorindex = @imagecolortransparent($image);
2381
            if($transcolorindex >= 0 ) { //transparent color exists
2382
                $transcolor = @imagecolorsforindex($image, $transcolorindex);
2383
                $transcolorindex = @imagecolorallocate(
2384
                    $newimg,
2385
                    $transcolor['red'],
2386
                    $transcolor['green'],
2387
                    $transcolor['blue']
2388
                );
2389
                @imagefill($newimg, 0, 0, $transcolorindex);
2390
                @imagecolortransparent($newimg, $transcolorindex);
2391
            }else{ //filling with white
2392
                $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2393
                @imagefill($newimg, 0, 0, $whitecolorindex);
2394
            }
2395
        }else{ //filling with white
2396
            $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2397
            @imagefill($newimg, 0, 0, $whitecolorindex);
2398
        }
2399
    }
2400
2401
    //try resampling first
2402
    if(function_exists("imagecopyresampled")){
2403
        if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) {
2404
            imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2405
        }
2406
    }else{
2407
        imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2408
    }
2409
2410
    $okay = false;
2411
    if ($ext == 'jpg' || $ext == 'jpeg'){
2412
        if(!function_exists('imagejpeg')){
2413
            $okay = false;
2414
        }else{
2415
            $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
2416
        }
2417
    }elseif($ext == 'png') {
2418
        if(!function_exists('imagepng')){
2419
            $okay = false;
2420
        }else{
2421
            $okay =  imagepng($newimg, $to);
2422
        }
2423
    }elseif($ext == 'gif') {
2424
        if(!function_exists('imagegif')){
2425
            $okay = false;
2426
        }else{
2427
            $okay = imagegif($newimg, $to);
2428
        }
2429
    }
2430
2431
    // destroy GD image ressources
2432
    if($image) imagedestroy($image);
2433
    if($newimg) imagedestroy($newimg);
2434
2435
    return $okay;
2436
}
2437
2438
/**
2439
 * Return other media files with the same base name
2440
 * but different extensions.
2441
 *
2442
 * @param string   $src     - ID of media file
2443
 * @param string[] $exts    - alternative extensions to find other files for
2444
 * @return array            - array(mime type => file ID)
2445
 *
2446
 * @author Anika Henke <[email protected]>
2447
 */
2448
function media_alternativefiles($src, $exts){
2449
2450
    $files = array();
2451
    list($srcExt, /* $srcMime */) = mimetype($src);
2452
    $filebase = substr($src, 0, -1 * (strlen($srcExt)+1));
2453
2454
    foreach($exts as $ext) {
2455
        $fileid = $filebase.'.'.$ext;
2456
        $file = mediaFN($fileid);
2457
        if(file_exists($file)) {
2458
            list(/* $fileExt */, $fileMime) = mimetype($file);
2459
            $files[$fileMime] = $fileid;
2460
        }
2461
    }
2462
    return $files;
2463
}
2464
2465
/**
2466
 * Check if video/audio is supported to be embedded.
2467
 *
2468
 * @param string $mime      - mimetype of media file
2469
 * @param string $type      - type of media files to check ('video', 'audio', or null for all)
2470
 * @return boolean
2471
 *
2472
 * @author Anika Henke <[email protected]>
2473
 */
2474
function media_supportedav($mime, $type=NULL){
2475
    $supportedAudio = array(
2476
        'ogg' => 'audio/ogg',
2477
        'mp3' => 'audio/mpeg',
2478
        'wav' => 'audio/wav',
2479
    );
2480
    $supportedVideo = array(
2481
        'webm' => 'video/webm',
2482
        'ogv' => 'video/ogg',
2483
        'mp4' => 'video/mp4',
2484
    );
2485
    if ($type == 'audio') {
2486
        $supportedAv = $supportedAudio;
2487
    } elseif ($type == 'video') {
2488
        $supportedAv = $supportedVideo;
2489
    } else {
2490
        $supportedAv = array_merge($supportedAudio, $supportedVideo);
2491
    }
2492
    return in_array($mime, $supportedAv);
2493
}
2494
2495
/**
2496
 * Return track media files with the same base name
2497
 * but extensions that indicate kind and lang.
2498
 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt...
2499
 *
2500
 * @param string   $src     - ID of media file
2501
 * @return array            - array(mediaID => array( kind, srclang ))
2502
 *
2503
 * @author Schplurtz le Déboulonné <[email protected]>
2504
 */
2505
function media_trackfiles($src){
2506
    $kinds=array(
2507
        'sub' => 'subtitles',
2508
        'cap' => 'captions',
2509
        'des' => 'descriptions',
2510
        'cha' => 'chapters',
2511
        'met' => 'metadata'
2512
    );
2513
2514
    $files = array();
2515
    $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/';
2516
    $baseid=pathinfo($src, PATHINFO_FILENAME);
2517
    $pattern=mediaFN($baseid).'.*.*.vtt';
2518
    $list=glob($pattern);
2519
    foreach($list as $track) {
2520
        if(preg_match($re, $track, $matches)){
2521
            $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=array(
2522
                $kinds[$matches[1]],
2523
                $matches[2],
2524
            );
2525
        }
2526
    }
2527
    return $files;
2528
}
2529
2530
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
2531