Failed Conditions
Push — refctorHTTPCLient ( 0efa8d...5a8d6e )
by Michael
04:15
created

inc/media.php (1 issue)

Labels
Severity

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;
0 ignored issues
show
This use statement conflicts with another class in this namespace, DokuHTTPClient.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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