Failed Conditions
Push — psr2-plugin ( 8df7ed )
by Andreas
09:08 queued 04:17
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;use dokuwiki\Extension\Event;
0 ignored issues
show
This use statement conflicts with another class in this namespace, MediaChangeLog.

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