Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/media.php (2 issues)

Upgrade to new PHP Analysis Engine

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

Code
1
<?php
2
/**
3
 * All output and handler function needed for the media management popup
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 */
8
9
use dokuwiki\ChangeLog\MediaChangeLog;
10
use dokuwiki\HTTP\DokuHTTPClient;
11
use dokuwiki\Subscriptions\MediaSubscriptionSender;
12
use dokuwiki\Extension\Event;
13
use dokuwiki\Form\Form;
14
use dokuwiki\Utf8\Sort;
15
16
/**
17
 * Lists pages which currently use a media file selected for deletion
18
 *
19
 * References uses the same visual as search results and share
20
 * their CSS tags except pagenames won't be links.
21
 *
22
 * @author Matthias Grimm <[email protected]>
23
 *
24
 * @param array $data
25
 * @param string $id
26
 */
27
function media_filesinuse($data,$id){
28
    global $lang;
29
    echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>';
30
    echo '<p>'.hsc($lang['ref_inuse']).'</p>';
31
32
    $hidden=0; //count of hits without read permission
33
    foreach($data as $row){
34
        if(auth_quickaclcheck($row) >= AUTH_READ && isVisiblePage($row)){
35
            echo '<div class="search_result">';
36
            echo '<span class="mediaref_ref">'.hsc($row).'</span>';
37
            echo '</div>';
38
        }else
39
            $hidden++;
40
    }
41
    if ($hidden){
42
        print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>';
43
    }
44
}
45
46
/**
47
 * Handles the saving of image meta data
48
 *
49
 * @author Andreas Gohr <[email protected]>
50
 * @author Kate Arzamastseva <[email protected]>
51
 *
52
 * @param string $id media id
53
 * @param int $auth permission level
54
 * @param array $data
55
 * @return false|string
56
 */
57
function media_metasave($id,$auth,$data){
58
    if($auth < AUTH_UPLOAD) return false;
59
    if(!checkSecurityToken()) return false;
60
    global $lang;
61
    global $conf;
62
    $src = mediaFN($id);
63
64
    $meta = new JpegMeta($src);
65
    $meta->_parseAll();
66
67
    foreach($data as $key => $val){
68
        $val=trim($val);
69
        if(empty($val)){
70
            $meta->deleteField($key);
71
        }else{
72
            $meta->setField($key,$val);
73
        }
74
    }
75
76
    $old = @filemtime($src);
77
    if(!file_exists(mediaFN($id, $old)) && file_exists($src)) {
78
        // add old revision to the attic
79
        media_saveOldRevision($id);
80
    }
81
    $filesize_old = filesize($src);
82
    if($meta->save()){
83
        if($conf['fperm']) chmod($src, $conf['fperm']);
84
        @clearstatcache(true, $src);
85
        $new = @filemtime($src);
86
        $filesize_new = filesize($src);
87
        $sizechange = $filesize_new - $filesize_old;
88
89
        // add a log entry to the media changelog
90
        addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange);
91
92
        msg($lang['metasaveok'],1);
93
        return $id;
94
    }else{
95
        msg($lang['metasaveerr'],-1);
96
        return false;
97
    }
98
}
99
100
/**
101
 * check if a media is external source
102
 *
103
 * @author Gerrit Uitslag <[email protected]>
104
 *
105
 * @param string $id the media ID or URL
106
 * @return bool
107
 */
108
function media_isexternal($id){
109
    if (preg_match('#^(?:https?|ftp)://#i', $id)) return true;
110
    return false;
111
}
112
113
/**
114
 * Check if a media item is public (eg, external URL or readable by @ALL)
115
 *
116
 * @author Andreas Gohr <[email protected]>
117
 *
118
 * @param string $id  the media ID or URL
119
 * @return bool
120
 */
121
function media_ispublic($id){
122
    if(media_isexternal($id)) return true;
123
    $id = cleanID($id);
124
    if(auth_aclcheck(getNS($id).':*', '', array()) >= AUTH_READ) return true;
125
    return false;
126
}
127
128
/**
129
 * Display the form to edit image meta data
130
 *
131
 * @author Andreas Gohr <[email protected]>
132
 * @author Kate Arzamastseva <[email protected]>
133
 *
134
 * @param string $id media id
135
 * @param int $auth permission level
136
 * @return bool
137
 */
138
function media_metaform($id, $auth) {
139
    global $lang;
140
141
    if ($auth < AUTH_UPLOAD) {
142
        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.DOKU_LF;
143
        return false;
144
    }
145
146
    // load the field descriptions
147
    static $fields = null;
148
    if ($fields === null) {
149
        $config_files = getConfigFiles('mediameta');
150
        foreach ($config_files as $config_file) {
151
            if (file_exists($config_file)) include($config_file);
152
        }
153
    }
154
155
    $src = mediaFN($id);
156
157
    // output
158
    $form = new Form([
159
            'action' => media_managerURL(['tab_details' => 'view'], '&'),
160
            'class' => 'meta'
161
    ]);
162
    $form->addTagOpen('div')->addClass('no');
163
    $form->setHiddenField('img', $id);
164
    $form->setHiddenField('mediado', 'save');
165
    foreach ($fields as $key => $field) {
166
        // get current value
167
        if (empty($field[0])) continue;
168
        $tags = array($field[0]);
169
        if (is_array($field[3])) $tags = array_merge($tags, $field[3]);
170
        $value = tpl_img_getTag($tags, '', $src);
171
        $value = cleanText($value);
172
173
        // prepare attributes
174
        $p = array(
175
            'class' => 'edit',
176
            'id'    => 'meta__'.$key,
177
            'name'  => 'meta['.$field[0].']',
178
        );
179
180
        $form->addTagOpen('div')->addClass('row');
181
        if ($field[2] == 'text') {
182
            $form->addTextInput(
183
                $p['name'],
184
                ($lang[$field[1]] ? $lang[$field[1]] : $field[1] . ':')
185
            )->id($p['id'])->addClass($p['class'])->val($value);
186
        } else {
187
            $form->addTextarea($p['name'], $lang[$field[1]])->id($p['id'])
188
                ->val(formText($value))
189
                ->addClass($p['class'])
190
                ->attr('rows', '6')->attr('cols', '50');
191
        }
192
        $form->addTagClose('div');
193
    }
194
    $form->addTagOpen('div')->addClass('buttons');
195
    $form->addButton('mediado[save]', $lang['btn_save'])->attr('type', 'submit')
196
        ->attrs(['accesskey' => 's']);
197
    $form->addTagClose('div');
198
199
    $form->addTagClose('div');
200
    echo $form->toHTML();
201
    return true;
202
}
203
204
/**
205
 * Convenience function to check if a media file is still in use
206
 *
207
 * @author Michael Klier <[email protected]>
208
 *
209
 * @param string $id media id
210
 * @return array|bool
211
 */
212
function media_inuse($id) {
213
    global $conf;
214
215
    if($conf['refcheck']){
216
        $mediareferences = ft_mediause($id,true);
217
        if(!count($mediareferences)) {
218
            return false;
219
        } else {
220
            return $mediareferences;
221
        }
222
    } else {
223
        return false;
224
    }
225
}
226
227
/**
228
 * Handles media file deletions
229
 *
230
 * If configured, checks for media references before deletion
231
 *
232
 * @author             Andreas Gohr <[email protected]>
233
 *
234
 * @param string $id media id
235
 * @param int $auth no longer used
236
 * @return int One of: 0,
237
 *                     DOKU_MEDIA_DELETED,
238
 *                     DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS,
239
 *                     DOKU_MEDIA_NOT_AUTH,
240
 *                     DOKU_MEDIA_INUSE
241
 */
242
function media_delete($id,$auth){
243
    global $lang;
244
    $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':'));
245
    if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH;
246
    if(media_inuse($id)) return DOKU_MEDIA_INUSE;
247
248
    $file = mediaFN($id);
249
250
    // trigger an event - MEDIA_DELETE_FILE
251
    $data = array();
252
    $data['id']   = $id;
253
    $data['name'] = \dokuwiki\Utf8\PhpString::basename($file);
254
    $data['path'] = $file;
255
    $data['size'] = (file_exists($file)) ? filesize($file) : 0;
256
257
    $data['unl'] = false;
258
    $data['del'] = false;
259
    $evt = new Event('MEDIA_DELETE_FILE',$data);
260
    if ($evt->advise_before()) {
261
        $old = @filemtime($file);
262
        if(!file_exists(mediaFN($id, $old)) && file_exists($file)) {
263
            // add old revision to the attic
264
            media_saveOldRevision($id);
265
        }
266
267
        $data['unl'] = @unlink($file);
268
        if($data['unl']) {
269
            $sizechange = 0 - $data['size'];
270
            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange);
271
272
            $data['del'] = io_sweepNS($id, 'mediadir');
273
        }
274
    }
275
    $evt->advise_after();
276
    unset($evt);
277
278
    if($data['unl'] && $data['del']){
279
        return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS;
280
    }
281
282
    return $data['unl'] ? DOKU_MEDIA_DELETED : 0;
283
}
284
285
/**
286
 * Handle file uploads via XMLHttpRequest
287
 *
288
 * @param string $ns   target namespace
289
 * @param int    $auth current auth check result
290
 * @return false|string false on error, id of the new file on success
291
 */
292
function media_upload_xhr($ns,$auth){
293
    if(!checkSecurityToken()) return false;
294
    global $INPUT;
295
296
    $id = $INPUT->get->str('qqfile');
297
    list($ext,$mime) = mimetype($id);
298
    $input = fopen("php://input", "r");
299
    if (!($tmp = io_mktmpdir())) return false;
300
    $path = $tmp.'/'.md5($id);
301
    $target = fopen($path, "w");
302
    $realSize = stream_copy_to_stream($input, $target);
303
    fclose($target);
304
    fclose($input);
305
    if (isset($_SERVER["CONTENT_LENGTH"]) && ($realSize != (int)$_SERVER["CONTENT_LENGTH"])){
306
        unlink($path);
307
        return false;
308
    }
309
310
    $res = media_save(
311
        array('name' => $path,
312
            'mime' => $mime,
313
            'ext'  => $ext),
314
        $ns.':'.$id,
315
        (($INPUT->get->str('ow') == 'true') ? true : false),
316
        $auth,
317
        'copy'
318
    );
319
    unlink($path);
320
    if ($tmp) io_rmdir($tmp, true);
321
    if (is_array($res)) {
322
        msg($res[0], $res[1]);
323
        return false;
324
    }
325
    return $res;
326
}
327
328
/**
329
 * Handles media file uploads
330
 *
331
 * @author Andreas Gohr <[email protected]>
332
 * @author Michael Klier <[email protected]>
333
 *
334
 * @param string     $ns    target namespace
335
 * @param int        $auth  current auth check result
336
 * @param bool|array $file  $_FILES member, $_FILES['upload'] if false
337
 * @return false|string false on error, id of the new file on success
338
 */
339
function media_upload($ns,$auth,$file=false){
340
    if(!checkSecurityToken()) return false;
341
    global $lang;
342
    global $INPUT;
343
344
    // get file and id
345
    $id   = $INPUT->post->str('mediaid');
346
    if (!$file) $file = $_FILES['upload'];
347
    if(empty($id)) $id = $file['name'];
348
349
    // check for errors (messages are done in lib/exe/mediamanager.php)
350
    if($file['error']) return false;
351
352
    // check extensions
353
    list($fext,$fmime) = mimetype($file['name']);
354
    list($iext,$imime) = mimetype($id);
355
    if($fext && !$iext){
356
        // no extension specified in id - read original one
357
        $id   .= '.'.$fext;
358
        $imime = $fmime;
359
    }elseif($fext && $fext != $iext){
360
        // extension was changed, print warning
361
        msg(sprintf($lang['mediaextchange'],$fext,$iext));
362
    }
363
364
    $res = media_save(array('name' => $file['tmp_name'],
365
                            'mime' => $imime,
366
                            'ext'  => $iext), $ns.':'.$id,
367
                      $INPUT->post->bool('ow'), $auth, 'copy_uploaded_file');
368
    if (is_array($res)) {
369
        msg($res[0], $res[1]);
370
        return false;
371
    }
372
    return $res;
373
}
374
375
/**
376
 * An alternative to move_uploaded_file that copies
377
 *
378
 * Using copy, makes sure any setgid bits on the media directory are honored
379
 *
380
 * @see   move_uploaded_file()
381
 *
382
 * @param string $from
383
 * @param string $to
384
 * @return bool
385
 */
386
function copy_uploaded_file($from, $to){
387
    if(!is_uploaded_file($from)) return false;
388
    $ok = copy($from, $to);
389
    @unlink($from);
390
    return $ok;
391
}
392
393
/**
394
 * This generates an action event and delegates to _media_upload_action().
395
 * Action plugins are allowed to pre/postprocess the uploaded file.
396
 * (The triggered event is preventable.)
397
 *
398
 * Event data:
399
 * $data[0]     fn_tmp:    the temporary file name (read from $_FILES)
400
 * $data[1]     fn:        the file name of the uploaded file
401
 * $data[2]     id:        the future directory id of the uploaded file
402
 * $data[3]     imime:     the mimetype of the uploaded file
403
 * $data[4]     overwrite: if an existing file is going to be overwritten
404
 * $data[5]     move:      name of function that performs move/copy/..
405
 *
406
 * @triggers MEDIA_UPLOAD_FINISH
407
 *
408
 * @param array  $file
409
 * @param string $id   media id
410
 * @param bool   $ow   overwrite?
411
 * @param int    $auth permission level
412
 * @param string $move name of functions that performs move/copy/..
413
 * @return false|array|string
414
 */
415
function media_save($file, $id, $ow, $auth, $move) {
416
    if($auth < AUTH_UPLOAD) {
417
        return array("You don't have permissions to upload files.", -1);
418
    }
419
420
    if (!isset($file['mime']) || !isset($file['ext'])) {
421
        list($ext, $mime) = mimetype($id);
422
        if (!isset($file['mime'])) {
423
            $file['mime'] = $mime;
424
        }
425
        if (!isset($file['ext'])) {
426
            $file['ext'] = $ext;
427
        }
428
    }
429
430
    global $lang, $conf;
431
432
    // get filename
433
    $id   = cleanID($id);
434
    $fn   = mediaFN($id);
435
436
    // get filetype regexp
437
    $types = array_keys(getMimeTypes());
438
    $types = array_map(
439
        function ($q) {
440
            return preg_quote($q, "/");
441
        },
442
        $types
443
    );
444
    $regex = join('|',$types);
445
446
    // because a temp file was created already
447
    if(!preg_match('/\.('.$regex.')$/i',$fn)) {
448
        return array($lang['uploadwrong'],-1);
449
    }
450
451
    //check for overwrite
452
    $overwrite = file_exists($fn);
453
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
454
    if($overwrite && (!$ow || $auth < $auth_ow)) {
455
        return array($lang['uploadexist'], 0);
456
    }
457
    // check for valid content
458
    $ok = media_contentcheck($file['name'], $file['mime']);
459
    if($ok == -1){
460
        return array(sprintf($lang['uploadbadcontent'],'.' . $file['ext']),-1);
461
    }elseif($ok == -2){
462
        return array($lang['uploadspam'],-1);
463
    }elseif($ok == -3){
464
        return array($lang['uploadxss'],-1);
465
    }
466
467
    // prepare event data
468
    $data = array();
469
    $data[0] = $file['name'];
470
    $data[1] = $fn;
471
    $data[2] = $id;
472
    $data[3] = $file['mime'];
473
    $data[4] = $overwrite;
474
    $data[5] = $move;
475
476
    // trigger event
477
    return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true);
478
}
479
480
/**
481
 * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH
482
 *
483
 * @author Michael Klier <[email protected]>
484
 *
485
 * @param array $data event data
486
 * @return false|array|string
487
 */
488
function _media_upload_action($data) {
489
    // fixme do further sanity tests of given data?
490
    if(is_array($data) && count($data)===6) {
491
        return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
492
    } else {
493
        return false; //callback error
494
    }
495
}
496
497
/**
498
 * Saves an uploaded media file
499
 *
500
 * @author Andreas Gohr <[email protected]>
501
 * @author Michael Klier <[email protected]>
502
 * @author Kate Arzamastseva <[email protected]>
503
 *
504
 * @param string $fn_tmp
505
 * @param string $fn
506
 * @param string $id        media id
507
 * @param string $imime     mime type
508
 * @param bool   $overwrite overwrite existing?
509
 * @param string $move      function name
510
 * @return array|string
511
 */
512
function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') {
513
    global $conf;
514
    global $lang;
515
    global $REV;
516
517
    $old = @filemtime($fn);
518
    if(!file_exists(mediaFN($id, $old)) && file_exists($fn)) {
519
        // add old revision to the attic if missing
520
        media_saveOldRevision($id);
521
    }
522
523
    // prepare directory
524
    io_createNamespace($id, 'media');
525
526
    $filesize_old = file_exists($fn) ? filesize($fn) : 0;
527
528
    if($move($fn_tmp, $fn)) {
529
        @clearstatcache(true,$fn);
530
        $new = @filemtime($fn);
531
        // Set the correct permission here.
532
        // Always chmod media because they may be saved with different permissions than expected from the php umask.
533
        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
534
        chmod($fn, $conf['fmode']);
535
        msg($lang['uploadsucc'],1);
536
        media_notify($id,$fn,$imime,$old,$new);
537
        // add a log entry to the media changelog
538
        $filesize_new = filesize($fn);
539
        $sizechange = $filesize_new - $filesize_old;
540
        if($REV) {
541
            addMediaLogEntry(
542
                $new,
543
                $id,
544
                DOKU_CHANGE_TYPE_REVERT,
545
                sprintf($lang['restored'], dformat($REV)),
546
                $REV,
547
                null,
548
                $sizechange
549
            );
550
        } elseif($overwrite) {
551
            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
552
        } else {
553
            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
554
        }
555
        return $id;
556
    }else{
557
        return array($lang['uploadfail'],-1);
558
    }
559
}
560
561
/**
562
 * Moves the current version of media file to the media_attic
563
 * directory
564
 *
565
 * @author Kate Arzamastseva <[email protected]>
566
 *
567
 * @param string $id
568
 * @return int - revision date
569
 */
570
function media_saveOldRevision($id){
571
    global $conf, $lang;
572
573
    $oldf = mediaFN($id);
574
    if(!file_exists($oldf)) return '';
575
    $date = filemtime($oldf);
576
    if (!$conf['mediarevisions']) return $date;
577
578
    $medialog = new MediaChangeLog($id);
579
    if (!$medialog->getRevisionInfo($date)) {
580
        // there was an external edit,
581
        // there is no log entry for current version of file
582
        $sizechange = filesize($oldf);
583
        if(!file_exists(mediaMetaFN($id, '.changes'))) {
584
            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
585
        } else {
586
            $oldRev = $medialog->getRevisions(-1, 1); // from changelog
587
            $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]);
588
            $filesize_old = filesize(mediaFN($id, $oldRev));
589
            $sizechange = $sizechange - $filesize_old;
590
591
            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
592
        }
593
    }
594
595
    $newf = mediaFN($id,$date);
596
    io_makeFileDir($newf);
597
    if(copy($oldf, $newf)) {
598
        // Set the correct permission here.
599
        // Always chmod media because they may be saved with different permissions than expected from the php umask.
600
        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
601
        chmod($newf, $conf['fmode']);
602
    }
603
    return $date;
604
}
605
606
/**
607
 * This function checks if the uploaded content is really what the
608
 * mimetype says it is. We also do spam checking for text types here.
609
 *
610
 * We need to do this stuff because we can not rely on the browser
611
 * to do this check correctly. Yes, IE is broken as usual.
612
 *
613
 * @author Andreas Gohr <[email protected]>
614
 * @link   http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting
615
 * @fixme  check all 26 magic IE filetypes here?
616
 *
617
 * @param string $file path to file
618
 * @param string $mime mimetype
619
 * @return int
620
 */
621
function media_contentcheck($file,$mime){
622
    global $conf;
623
    if($conf['iexssprotect']){
624
        $fh = @fopen($file, 'rb');
625
        if($fh){
626
            $bytes = fread($fh, 256);
627
            fclose($fh);
628
            if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i',$bytes)){
629
                return -3; //XSS: possibly malicious content
630
            }
631
        }
632
    }
633
    if(substr($mime,0,6) == 'image/'){
634
        $info = @getimagesize($file);
635
        if($mime == 'image/gif' && $info[2] != 1){
636
            return -1; // uploaded content did not match the file extension
637
        }elseif($mime == 'image/jpeg' && $info[2] != 2){
638
            return -1;
639
        }elseif($mime == 'image/png' && $info[2] != 3){
640
            return -1;
641
        }
642
        # fixme maybe check other images types as well
643
    }elseif(substr($mime,0,5) == 'text/'){
644
        global $TEXT;
645
        $TEXT = io_readFile($file);
646
        if(checkwordblock()){
647
            return -2; //blocked by the spam blacklist
648
        }
649
    }
650
    return 0;
651
}
652
653
/**
654
 * Send a notify mail on uploads
655
 *
656
 * @author Andreas Gohr <[email protected]>
657
 *
658
 * @param string   $id      media id
659
 * @param string   $file    path to file
660
 * @param string   $mime    mime type
661
 * @param bool|int $old_rev revision timestamp or false
662
 * @return bool
663
 */
664
function media_notify($id,$file,$mime,$old_rev=false,$current_rev=false){
665
    global $conf;
666
    if(empty($conf['notify'])) return false; //notify enabled?
667
668
    $subscription = new MediaSubscriptionSender();
669
    return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev);
670
}
671
672
/**
673
 * List all files in a given Media namespace
674
 *
675
 * @param string      $ns             namespace
676
 * @param null|int    $auth           permission level
677
 * @param string      $jump           id
678
 * @param bool        $fullscreenview
679
 * @param bool|string $sort           sorting order, false skips sorting
680
 */
681
function media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){
682
    global $conf;
683
    global $lang;
684
    $ns = cleanID($ns);
685
686
    // check auth our self if not given (needed for ajax calls)
687
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
688
689
    if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
690
691
    if($auth < AUTH_READ){
692
        // FIXME: print permission warning here instead?
693
        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
694
    }else{
695
        if (!$fullscreenview) {
696
            media_uploadform($ns, $auth);
697
            media_searchform($ns);
698
        }
699
700
        $dir = utf8_encodeFN(str_replace(':','/',$ns));
701
        $data = array();
702
        search($data,$conf['mediadir'],'search_mediafiles',
703
                array('showmsg'=>true,'depth'=>1),$dir,1,$sort);
704
705
        if(!count($data)){
706
            echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
707
        }else {
708
            if ($fullscreenview) {
709
                echo '<ul class="' . _media_get_list_type() . '">';
710
            }
711
            foreach($data as $item){
712
                if (!$fullscreenview) {
713
                    //FIXME old call: media_printfile($item,$auth,$jump);
714
                    $display = new \dokuwiki\Ui\Media\DisplayRow($item);
715
                    $display->scrollIntoView($jump == $item->getID());
716
                    $display->show();
717
                } else {
718
                    //FIXME old call: media_printfile_thumbs($item,$auth,$jump);
719
                    echo '<li>';
720
                    $display = new \dokuwiki\Ui\Media\DisplayTile($item);
721
                    $display->scrollIntoView($jump == $item->getID());
722
                    $display->show();
723
                    echo '</li>';
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(['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(['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(['tab_details' => 'edit'], '&'),
770
                              'caption' => $lang['media_edittab']);
771
    }
772
    if ($conf['mediarevisions']) {
773
        $tabs['history'] = array('href'    => media_managerURL(['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
790
    $form = new Form([
791
            'method' => 'get',
792
            'action' => wl($ID),
793
            'class' => 'options'
794
    ]);
795
    $form->addTagOpen('div')->addClass('no');
796
    $form->setHiddenField('sectok', null);
797
    $media_manager_params = media_managerURL([], '', false, true);
798
    foreach ($media_manager_params as $pKey => $pVal) {
799
        $form->setHiddenField($pKey, $pVal);
800
    }
801
    if ($INPUT->has('q')) {
802
        $form->setHiddenField('q', $INPUT->str('q'));
803
    }
804
    $form->addHTML('<ul>'.NL);
805
    foreach (array('list' => array('listType', array('thumbs', 'rows')),
806
                  'sort' => array('sortBy', array('name', 'date')))
807
            as $group => $content) {
808
        $checked = "_media_get_${group}_type";
809
        $checked = $checked();
810
811
        $form->addHTML('<li class="'. $content[0] .'">');
812
        foreach ($content[1] as $option) {
813
            $attrs = array();
814
            if ($checked == $option) {
815
                $attrs['checked'] = 'checked';
816
            }
817
            $radio = $form->addRadioButton(
818
                $group.'_dwmedia',
819
                $lang['media_'.$group.'_'.$option]
820
            )->val($option)->id($content[0].'__'.$option)->addClass($option);
821
            $radio->attrs($attrs);
822
        }
823
        $form->addHTML('</li>'.NL);
824
    }
825
    $form->addHTML('<li>');
826
    $form->addButton('', $lang['btn_apply'])->attr('type', 'submit');
827
    $form->addHTML('</li>'.NL);
828
    $form->addHTML('</ul>'.NL);
829
    $form->addTagClose('div');
830
    print $form->toHTML();
831
}
832
833
/**
834
 * Returns type of sorting for the list of files in media manager
835
 *
836
 * @author Kate Arzamastseva <[email protected]>
837
 *
838
 * @return string - sort type
839
 */
840
function _media_get_sort_type() {
841
    return _media_get_display_param('sort', array('default' => 'name', 'date'));
842
}
843
844
/**
845
 * Returns type of listing for the list of files in media manager
846
 *
847
 * @author Kate Arzamastseva <[email protected]>
848
 *
849
 * @return string - list type
850
 */
851
function _media_get_list_type() {
852
    return _media_get_display_param('list', array('default' => 'thumbs', 'rows'));
853
}
854
855
/**
856
 * Get display parameters
857
 *
858
 * @param string $param   name of parameter
859
 * @param array  $values  allowed values, where default value has index key 'default'
860
 * @return string the parameter value
861
 */
862
function _media_get_display_param($param, $values) {
863
    global $INPUT;
864
    if (in_array($INPUT->str($param), $values)) {
865
        // FIXME: Set cookie
866
        return $INPUT->str($param);
867
    } else {
868
        $val = get_doku_pref($param, $values['default']);
869
        if (!in_array($val, $values)) {
870
            $val = $values['default'];
871
        }
872
        return $val;
873
    }
874
}
875
876
/**
877
 * Prints tab that displays a list of all files
878
 *
879
 * @author Kate Arzamastseva <[email protected]>
880
 *
881
 * @param string    $ns
882
 * @param null|int  $auth permission level
883
 * @param string    $jump item id
884
 */
885
function media_tab_files($ns,$auth=null,$jump='') {
886
    global $lang;
887
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
888
889
    if($auth < AUTH_READ){
890
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
891
    }else{
892
        media_filelist($ns,$auth,$jump,true,_media_get_sort_type());
893
    }
894
}
895
896
/**
897
 * Prints tab that displays uploading form
898
 *
899
 * @author Kate Arzamastseva <[email protected]>
900
 *
901
 * @param string   $ns
902
 * @param null|int $auth permission level
903
 * @param string   $jump item id
904
 */
905
function media_tab_upload($ns,$auth=null,$jump='') {
906
    global $lang;
907
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
908
909
    echo '<div class="upload">'.NL;
910
    if ($auth >= AUTH_UPLOAD) {
911
        echo '<p>' . $lang['mediaupload'] . '</p>';
912
    }
913
    media_uploadform($ns, $auth, true);
914
    echo '</div>'.NL;
915
}
916
917
/**
918
 * Prints tab that displays search form
919
 *
920
 * @author Kate Arzamastseva <[email protected]>
921
 *
922
 * @param string $ns
923
 * @param null|int $auth permission level
924
 */
925
function media_tab_search($ns,$auth=null) {
926
    global $INPUT;
927
928
    $do = $INPUT->str('mediado');
929
    $query = $INPUT->str('q');
930
    echo '<div class="search">'.NL;
931
932
    media_searchform($ns, $query, true);
933
    if ($do == 'searchlist' || $query) {
934
        media_searchlist($query,$ns,$auth,true,_media_get_sort_type());
935
    }
936
    echo '</div>'.NL;
937
}
938
939
/**
940
 * Prints tab that displays mediafile details
941
 *
942
 * @author Kate Arzamastseva <[email protected]>
943
 *
944
 * @param string     $image media id
945
 * @param string     $ns
946
 * @param null|int   $auth  permission level
947
 * @param string|int $rev   revision timestamp or empty string
948
 */
949
function media_tab_view($image, $ns, $auth=null, $rev='') {
950
    global $lang;
951
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
952
953
    if ($image && $auth >= AUTH_READ) {
954
        $meta = new JpegMeta(mediaFN($image, $rev));
955
        media_preview($image, $auth, $rev, $meta);
956
        media_preview_buttons($image, $auth, $rev);
957
        media_details($image, $auth, $rev, $meta);
958
959
    } else {
960
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
961
    }
962
}
963
964
/**
965
 * Prints tab that displays form for editing mediafile metadata
966
 *
967
 * @author Kate Arzamastseva <[email protected]>
968
 *
969
 * @param string     $image media id
970
 * @param string     $ns
971
 * @param null|int   $auth permission level
972
 */
973
function media_tab_edit($image, $ns, $auth=null) {
974
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
975
976
    if ($image) {
977
        list(, $mime) = mimetype($image);
978
        if ($mime == 'image/jpeg') media_metaform($image,$auth);
979
    }
980
}
981
982
/**
983
 * Prints tab that displays mediafile revisions
984
 *
985
 * @author Kate Arzamastseva <[email protected]>
986
 *
987
 * @param string     $image media id
988
 * @param string     $ns
989
 * @param null|int   $auth permission level
990
 */
991
function media_tab_history($image, $ns, $auth=null) {
992
    global $lang;
993
    global $INPUT;
994
995
    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
996
    $do = $INPUT->str('mediado');
997
998
    if ($auth >= AUTH_READ && $image) {
999
        if ($do == 'diff'){
1000
            media_diff($image, $ns, $auth);
1001
        } else {
1002
            $first = $INPUT->int('first');
1003
            html_revisions($first, $image);
1004
        }
1005
    } else {
1006
        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1007
    }
1008
}
1009
1010
/**
1011
 * Prints mediafile details
1012
 *
1013
 * @param string         $image media id
1014
 * @param int            $auth permission level
1015
 * @param int|string     $rev revision timestamp or empty string
1016
 * @param JpegMeta|bool  $meta
1017
 *
1018
 * @author Kate Arzamastseva <[email protected]>
1019
 */
1020
function media_preview($image, $auth, $rev='', $meta=false) {
1021
1022
    $size = media_image_preview_size($image, $rev, $meta);
1023
1024
    if ($size) {
1025
        global $lang;
1026
        echo '<div class="image">';
1027
1028
        $more = array();
1029
        if ($rev) {
1030
            $more['rev'] = $rev;
1031
        } else {
1032
            $t = @filemtime(mediaFN($image));
1033
            $more['t'] = $t;
1034
        }
1035
1036
        $more['w'] = $size[0];
1037
        $more['h'] = $size[1];
1038
        $src = ml($image, $more);
1039
1040
        echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">';
1041
        echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
1042
        echo '</a>';
1043
1044
        echo '</div>'.NL;
1045
    }
1046
}
1047
1048
/**
1049
 * Prints mediafile action buttons
1050
 *
1051
 * @author Kate Arzamastseva <[email protected]>
1052
 *
1053
 * @param string     $image media id
1054
 * @param int        $auth  permission level
1055
 * @param string|int $rev   revision timestamp, or empty string
1056
 */
1057
function media_preview_buttons($image, $auth, $rev = '') {
1058
    global $lang, $conf;
1059
1060
    echo '<ul class="actions">'.DOKU_LF;
1061
1062
    if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) {
1063
1064
        // delete button
1065
        $form = new Form([
1066
            'id' => 'mediamanager__btn_delete',
1067
            'action' => media_managerURL(['delete' => $image], '&'),
1068
        ]);
1069
        $form->addTagOpen('div')->addClass('no');
1070
        $form->addButton('', $lang['btn_delete'])->attr('type', 'submit');
1071
        $form->addTagClose('div');
1072
        echo '<li>';
1073
        echo $form->toHTML();
1074
        echo '</li>'.DOKU_LF;
1075
    }
1076
1077
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1078
    if ($auth >= $auth_ow && !$rev) {
1079
1080
        // upload new version button
1081
        $form = new Form([
1082
            'id' => 'mediamanager__btn_update',
1083
            'action' => media_managerURL(['image' => $image, 'mediado' => 'update'], '&'),
1084
        ]);
1085
        $form->addTagOpen('div')->addClass('no');
1086
        $form->addButton('', $lang['media_update'])->attr('type', 'submit');
1087
        $form->addTagClose('div');
1088
        echo '<li>';
1089
        echo $form->toHTML();
1090
        echo '</li>'.DOKU_LF;
1091
    }
1092
1093
    if ($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))) {
1094
1095
        // restore button
1096
        $form = new Form([
1097
            'id' => 'mediamanager__btn_restore',
1098
            'action'=>media_managerURL(['image' => $image], '&'),
1099
        ]);
1100
        $form->addTagOpen('div')->addClass('no');
1101
        $form->setHiddenField('mediado', 'restore');
1102
        $form->setHiddenField('rev', $rev);
1103
        $form->addButton('', $lang['media_restore'])->attr('type', 'submit');
1104
        $form->addTagClose('div');
1105
        echo '<li>';
1106
        echo $form->toHTML();
1107
        echo '</li>'.DOKU_LF;
1108
    }
1109
1110
    echo '</ul>'.DOKU_LF;
1111
}
1112
1113
/**
1114
 * Returns image width and height for mediamanager preview panel
1115
 *
1116
 * @author Kate Arzamastseva <[email protected]>
1117
 * @param string         $image
1118
 * @param int|string     $rev
1119
 * @param JpegMeta|bool  $meta
1120
 * @param int            $size
1121
 * @return array|false
1122
 */
1123
function media_image_preview_size($image, $rev, $meta, $size = 500) {
1124
    if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false;
1125
1126
    $info = getimagesize(mediaFN($image, $rev));
1127
    $w = (int) $info[0];
1128
    $h = (int) $info[1];
1129
1130
    if($meta && ($w > $size || $h > $size)){
1131
        $ratio = $meta->getResizeRatio($size, $size);
1132
        $w = floor($w * $ratio);
1133
        $h = floor($h * $ratio);
1134
    }
1135
    return array($w, $h);
1136
}
1137
1138
/**
1139
 * Returns the requested EXIF/IPTC tag from the image meta
1140
 *
1141
 * @author Kate Arzamastseva <[email protected]>
1142
 *
1143
 * @param array    $tags array with tags, first existing is returned
1144
 * @param JpegMeta $meta
1145
 * @param string   $alt  alternative value
1146
 * @return string
1147
 */
1148
function media_getTag($tags,$meta,$alt=''){
1149
    if($meta === false) return $alt;
1150
    $info = $meta->getField($tags);
1151
    if($info == false) return $alt;
1152
    return $info;
1153
}
1154
1155
/**
1156
 * Returns mediafile tags
1157
 *
1158
 * @author Kate Arzamastseva <[email protected]>
1159
 *
1160
 * @param JpegMeta $meta
1161
 * @return array list of tags of the mediafile
1162
 */
1163
function media_file_tags($meta) {
1164
    // load the field descriptions
1165
    static $fields = null;
1166
    if(is_null($fields)){
1167
        $config_files = getConfigFiles('mediameta');
1168
        foreach ($config_files as $config_file) {
1169
            if(file_exists($config_file)) include($config_file);
1170
        }
1171
    }
1172
1173
    $tags = array();
1174
1175
    foreach($fields as $key => $tag){
1176
        $t = array();
1177
        if (!empty($tag[0])) $t = array($tag[0]);
1178
        if(isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]);
1179
        $value = media_getTag($t, $meta);
1180
        $tags[] = array('tag' => $tag, 'value' => $value);
1181
    }
1182
1183
    return $tags;
1184
}
1185
1186
/**
1187
 * Prints mediafile tags
1188
 *
1189
 * @author Kate Arzamastseva <[email protected]>
1190
 *
1191
 * @param string        $image image id
1192
 * @param int           $auth  permission level
1193
 * @param string|int    $rev   revision timestamp, or empty string
1194
 * @param bool|JpegMeta $meta  image object, or create one if false
1195
 */
1196
function media_details($image, $auth, $rev='', $meta=false) {
1197
    global $lang;
1198
1199
    if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
1200
    $tags = media_file_tags($meta);
1201
1202
    echo '<dl>'.NL;
1203
    foreach($tags as $tag){
1204
        if ($tag['value']) {
1205
            $value = cleanText($tag['value']);
1206
            echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>';
1207
            if ($tag['tag'][2] == 'date') echo dformat($value);
1208
            else echo hsc($value);
1209
            echo '</dd>'.NL;
1210
        }
1211
    }
1212
    echo '</dl>'.NL;
1213
    echo '<dl>'.NL;
1214
    echo '<dt>'.$lang['reference'].':</dt>';
1215
    $media_usage = ft_mediause($image,true);
1216
    if(count($media_usage) > 0){
1217
        foreach($media_usage as $path){
1218
            echo '<dd>'.html_wikilink($path).'</dd>';
1219
        }
1220
    }else{
1221
        echo '<dd>'.$lang['nothingfound'].'</dd>';
1222
    }
1223
    echo '</dl>'.NL;
1224
1225
}
1226
1227
/**
1228
 * Shows difference between two revisions of file
1229
 *
1230
 * @author Kate Arzamastseva <[email protected]>
1231
 *
1232
 * @param string $image  image id
1233
 * @param string $ns
1234
 * @param int $auth permission level
1235
 * @param bool $fromajax
1236
 * @return false|null|string
1237
 */
1238
function media_diff($image, $ns, $auth, $fromajax = false) {
1239
    global $conf;
1240
    global $INPUT;
1241
1242
    if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return '';
1243
1244
    $rev1 = $INPUT->int('rev');
1245
1246
    $rev2 = $INPUT->ref('rev2');
1247
    if(is_array($rev2)){
1248
        $rev1 = (int) $rev2[0];
1249
        $rev2 = (int) $rev2[1];
1250
1251
        if(!$rev1){
1252
            $rev1 = $rev2;
1253
            unset($rev2);
1254
        }
1255
    }else{
1256
        $rev2 = $INPUT->int('rev2');
1257
    }
1258
1259
    if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false;
1260
    if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false;
1261
1262
    if($rev1 && $rev2){            // two specific revisions wanted
1263
        // make sure order is correct (older on the left)
1264
        if($rev1 < $rev2){
1265
            $l_rev = $rev1;
1266
            $r_rev = $rev2;
1267
        }else{
1268
            $l_rev = $rev2;
1269
            $r_rev = $rev1;
1270
        }
1271
    }elseif($rev1){                // single revision given, compare to current
1272
        $r_rev = '';
1273
        $l_rev = $rev1;
1274
    }else{                        // no revision was given, compare previous to current
1275
        $r_rev = '';
1276
        $medialog = new MediaChangeLog($image);
1277
        $revs = $medialog->getRevisions(0, 1);
1278
        if (file_exists(mediaFN($image, $revs[0]))) {
1279
            $l_rev = $revs[0];
1280
        } else {
1281
            $l_rev = '';
1282
        }
1283
    }
1284
1285
    // prepare event data
1286
    $data = array();
1287
    $data[0] = $image;
1288
    $data[1] = $l_rev;
1289
    $data[2] = $r_rev;
1290
    $data[3] = $ns;
1291
    $data[4] = $auth;
1292
    $data[5] = $fromajax;
1293
1294
    // trigger event
1295
    return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true);
1296
}
1297
1298
/**
1299
 * Callback for media file diff
1300
 *
1301
 * @param array $data event data
1302
 * @return false|null
1303
 */
1304
function _media_file_diff($data) {
1305
    if(is_array($data) && count($data)===6) {
1306
        media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
1307
    } else {
1308
        return false;
1309
    }
1310
}
1311
1312
/**
1313
 * Shows difference between two revisions of image
1314
 *
1315
 * @author Kate Arzamastseva <[email protected]>
1316
 *
1317
 * @param string $image
1318
 * @param string|int $l_rev revision timestamp, or empty string
1319
 * @param string|int $r_rev revision timestamp, or empty string
1320
 * @param string $ns
1321
 * @param int $auth permission level
1322
 * @param bool $fromajax
1323
 */
1324
function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax) {
1325
    global $lang;
1326
    global $INPUT;
1327
1328
    $l_meta = new JpegMeta(mediaFN($image, $l_rev));
1329
    $r_meta = new JpegMeta(mediaFN($image, $r_rev));
1330
1331
    $is_img = preg_match('/\.(jpe?g|gif|png)$/', $image);
1332
    if ($is_img) {
1333
        $l_size = media_image_preview_size($image, $l_rev, $l_meta);
1334
        $r_size = media_image_preview_size($image, $r_rev, $r_meta);
1335
        $is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30));
1336
1337
        $difftype = $INPUT->str('difftype');
1338
1339
        if (!$fromajax) {
1340
            $form = new Form([
1341
                'id' => 'mediamanager__form_diffview',
1342
                'action' => media_managerURL([], '&'),
1343
                'method' => 'get',
1344
                'class' => 'diffView',
1345
            ]);
1346
            $form->addTagOpen('div')->addClass('no');
1347
            $form->setHiddenField('sectok', null);
1348
            $form->setHiddenField('mediado', 'diff');
1349
            $form->setHiddenField('rev2[0]', $l_rev);
1350
            $form->setHiddenField('rev2[1]', $r_rev);
1351
            echo $form->toHTML();
1352
1353
            echo NL.'<div id="mediamanager__diff" >'.NL;
1354
        }
1355
1356
        if ($difftype == 'opacity' || $difftype == 'portions') {
1357
            media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype);
0 ignored issues
show
It seems like $l_size defined by media_image_preview_size($image, $l_rev, $l_meta) on line 1333 can also be of type false; however, media_image_diff() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
It seems like $r_size defined by media_image_preview_size($image, $r_rev, $r_meta) on line 1334 can also be of type false; however, media_image_diff() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1358
            if (!$fromajax) echo '</div>';
1359
            return;
1360
        }
1361
    }
1362
1363
    list($l_head, $r_head) = (new dokuwiki\Ui\Diff)->diffHead($l_rev, $r_rev, $image, true);
1364
1365
    ?>
1366
    <div class="table">
1367
    <table>
1368
      <tr>
1369
        <th><?php echo $l_head; ?></th>
1370
        <th><?php echo $r_head; ?></th>
1371
      </tr>
1372
    <?php
1373
1374
    echo '<tr class="image">';
1375
    echo '<td>';
1376
    media_preview($image, $auth, $l_rev, $l_meta);
1377
    echo '</td>';
1378
1379
    echo '<td>';
1380
    media_preview($image, $auth, $r_rev, $r_meta);
1381
    echo '</td>';
1382
    echo '</tr>'.NL;
1383
1384
    echo '<tr class="actions">';
1385
    echo '<td>';
1386
    media_preview_buttons($image, $auth, $l_rev);
1387
    echo '</td>';
1388
1389
    echo '<td>';
1390
    media_preview_buttons($image, $auth, $r_rev);
1391
    echo '</td>';
1392
    echo '</tr>'.NL;
1393
1394
    $l_tags = media_file_tags($l_meta);
1395
    $r_tags = media_file_tags($r_meta);
1396
    // FIXME r_tags-only stuff
1397
    foreach ($l_tags as $key => $l_tag) {
1398
        if ($l_tag['value'] != $r_tags[$key]['value']) {
1399
            $r_tags[$key]['highlighted'] = true;
1400
            $l_tags[$key]['highlighted'] = true;
1401
        } else if (!$l_tag['value'] || !$r_tags[$key]['value']) {
1402
            unset($r_tags[$key]);
1403
            unset($l_tags[$key]);
1404
        }
1405
    }
1406
1407
    echo '<tr>';
1408
    foreach(array($l_tags,$r_tags) as $tags){
1409
        echo '<td>'.NL;
1410
1411
        echo '<dl class="img_tags">';
1412
        foreach($tags as $tag){
1413
            $value = cleanText($tag['value']);
1414
            if (!$value) $value = '-';
1415
            echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
1416
            echo '<dd>';
1417
            if ($tag['highlighted']) {
1418
                echo '<strong>';
1419
            }
1420
            if ($tag['tag'][2] == 'date') echo dformat($value);
1421
            else echo hsc($value);
1422
            if ($tag['highlighted']) {
1423
                echo '</strong>';
1424
            }
1425
            echo '</dd>';
1426
        }
1427
        echo '</dl>'.NL;
1428
1429
        echo '</td>';
1430
    }
1431
    echo '</tr>'.NL;
1432
1433
    echo '</table>'.NL;
1434
    echo '</div>'.NL;
1435
1436
    if ($is_img && !$fromajax) echo '</div>';
1437
}
1438
1439
/**
1440
 * Prints two images side by side
1441
 * and slider
1442
 *
1443
 * @author Kate Arzamastseva <[email protected]>
1444
 *
1445
 * @param string $image   image id
1446
 * @param int    $l_rev   revision timestamp, or empty string
1447
 * @param int    $r_rev   revision timestamp, or empty string
1448
 * @param array  $l_size  array with width and height
1449
 * @param array  $r_size  array with width and height
1450
 * @param string $type
1451
 */
1452
function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) {
1453
    if ($l_size != $r_size) {
1454
        if ($r_size[0] > $l_size[0]) {
1455
            $l_size = $r_size;
1456
        }
1457
    }
1458
1459
    $l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1460
    $r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1461
1462
    $l_src = ml($image, $l_more);
1463
    $r_src = ml($image, $r_more);
1464
1465
    // slider
1466
    echo '<div class="slider" style="max-width: '.($l_size[0]-20).'px;" ></div>'.NL;
1467
1468
    // two images in divs
1469
    echo '<div class="imageDiff ' . $type . '">'.NL;
1470
    echo '<div class="image1" style="max-width: '.$l_size[0].'px;">';
1471
    echo '<img src="'.$l_src.'" alt="" />';
1472
    echo '</div>'.NL;
1473
    echo '<div class="image2" style="max-width: '.$l_size[0].'px;">';
1474
    echo '<img src="'.$r_src.'" alt="" />';
1475
    echo '</div>'.NL;
1476
    echo '</div>'.NL;
1477
}
1478
1479
/**
1480
 * Restores an old revision of a media file
1481
 *
1482
 * @param string $image media id
1483
 * @param int    $rev   revision timestamp or empty string
1484
 * @param int    $auth
1485
 * @return string - file's id
1486
 *
1487
 * @author Kate Arzamastseva <[email protected]>
1488
 */
1489
function media_restore($image, $rev, $auth){
1490
    global $conf;
1491
    if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
1492
    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
1493
    if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
1494
    if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
1495
    list(,$imime,) = mimetype($image);
1496
    $res = media_upload_finish(mediaFN($image, $rev),
1497
        mediaFN($image),
1498
        $image,
1499
        $imime,
1500
        true,
1501
        'copy');
1502
    if (is_array($res)) {
1503
        msg($res[0], $res[1]);
1504
        return false;
1505
    }
1506
    return $res;
1507
}
1508
1509
/**
1510
 * List all files found by the search request
1511
 *
1512
 * @author Tobias Sarnowski <[email protected]>
1513
 * @author Andreas Gohr <[email protected]>
1514
 * @author Kate Arzamastseva <[email protected]>
1515
 * @triggers MEDIA_SEARCH
1516
 *
1517
 * @param string $query
1518
 * @param string $ns
1519
 * @param null|int $auth
1520
 * @param bool $fullscreen
1521
 * @param string $sort
1522
 */
1523
function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort='natural'){
1524
    global $conf;
1525
    global $lang;
1526
1527
    $ns = cleanID($ns);
1528
    $evdata = array(
1529
        'ns'    => $ns,
1530
        'data'  => array(),
1531
        'query' => $query
1532
    );
1533
    if (!blank($query)) {
1534
        $evt = new Event('MEDIA_SEARCH', $evdata);
1535
        if ($evt->advise_before()) {
1536
            $dir = utf8_encodeFN(str_replace(':','/',$evdata['ns']));
1537
            $quoted = preg_quote($evdata['query'],'/');
1538
            //apply globbing
1539
            $quoted = str_replace(array('\*', '\?'), array('.*', '.'), $quoted, $count);
1540
1541
            //if we use globbing file name must match entirely but may be preceded by arbitrary namespace
1542
            if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$';
1543
1544
            $pattern = '/'.$quoted.'/i';
1545
            search($evdata['data'],
1546
                    $conf['mediadir'],
1547
                    'search_mediafiles',
1548
                    array('showmsg'=>false,'pattern'=>$pattern),
1549
                    $dir,
1550
                    1,
1551
                    $sort);
1552
        }
1553
        $evt->advise_after();
1554
        unset($evt);
1555
    }
1556
1557
    if (!$fullscreen) {
1558
        echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL;
1559
        media_searchform($ns,$query);
1560
    }
1561
1562
    if(!count($evdata['data'])){
1563
        echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
1564
    }else {
1565
        if ($fullscreen) {
1566
            echo '<ul class="' . _media_get_list_type() . '">';
1567
        }
1568
        foreach($evdata['data'] as $item){
1569
            if (!$fullscreen) {
1570
                // FIXME old call: media_printfile($item,$item['perm'],'',true);
1571
                $display = new \dokuwiki\Ui\Media\DisplayRow($item);
1572
                $display->relativeDisplay($ns);
1573
                $display->show();
1574
            } else {
1575
                // FIXME old call: media_printfile_thumbs($item,$item['perm'],false,true);
1576
                $display = new \dokuwiki\Ui\Media\DisplayTile($item);
1577
                $display->relativeDisplay($ns);
1578
                echo '<li>';
1579
                $display->show();
1580
                echo '</li>';
1581
            }
1582
        }
1583
        if ($fullscreen) echo '</ul>'.NL;
1584
    }
1585
}
1586
1587
/**
1588
 * Display a media icon
1589
 *
1590
 * @param string $filename media id
1591
 * @param string $size     the size subfolder, if not specified 16x16 is used
1592
 * @return string html
1593
 */
1594
function media_printicon($filename, $size=''){
1595
    list($ext) = mimetype(mediaFN($filename),false);
1596
1597
    if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) {
1598
        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png';
1599
    } else {
1600
        $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png';
1601
    }
1602
1603
    return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
1604
}
1605
1606
/**
1607
 * Build link based on the current, adding/rewriting parameters
1608
 *
1609
 * @author Kate Arzamastseva <[email protected]>
1610
 *
1611
 * @param array|bool $params
1612
 * @param string     $amp           separator
1613
 * @param bool       $abs           absolute url?
1614
 * @param bool       $params_array  return the parmeters array?
1615
 * @return string|array - link or link parameters
1616
 */
1617
function media_managerURL($params = false, $amp = '&amp;', $abs = false, $params_array = false) {
1618
    global $ID;
1619
    global $INPUT;
1620
1621
    $gets = array('do' => 'media');
1622
    $media_manager_params = array('tab_files', 'tab_details', 'image', 'ns', 'list', 'sort');
1623
    foreach ($media_manager_params as $x) {
1624
        if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x);
1625
    }
1626
1627
    if ($params) {
1628
        $gets = $params + $gets;
1629
    }
1630
    unset($gets['id']);
1631
    if (isset($gets['delete'])) {
1632
        unset($gets['image']);
1633
        unset($gets['tab_details']);
1634
    }
1635
1636
    if ($params_array) return $gets;
1637
1638
    return wl($ID,$gets,$abs,$amp);
1639
}
1640
1641
/**
1642
 * Print the media upload form if permissions are correct
1643
 *
1644
 * @author Andreas Gohr <[email protected]>
1645
 * @author Kate Arzamastseva <[email protected]>
1646
 *
1647
 * @param string $ns
1648
 * @param int    $auth permission level
1649
 * @param bool  $fullscreen
1650
 */
1651
function media_uploadform($ns, $auth, $fullscreen = false) {
1652
    global $lang;
1653
    global $conf;
1654
    global $INPUT;
1655
1656
    if ($auth < AUTH_UPLOAD) {
1657
        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
1658
        return;
1659
    }
1660
    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1661
1662
    $update = false;
1663
    $id = '';
1664
    if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') {
1665
        $update = true;
1666
        $id = cleanID($INPUT->str('image'));
1667
    }
1668
1669
    // The default HTML upload form
1670
    $form = new Form([
1671
        'id' => 'dw__upload',
1672
        'enctype' => 'multipart/form-data',
1673
        'action' => ($fullscreen)
1674
                    ? media_managerURL(['tab_files' => 'files', 'tab_details' => 'view'], '&')
1675
                    : DOKU_BASE.'lib/exe/mediamanager.php',
1676
    ]);
1677
    $form->addTagOpen('div')->addClass('no');
1678
    $form->setHiddenField('ns', hsc($ns));  // FIXME hsc required?
1679
    $form->addTagOpen('p');
1680
    $form->addTextInput('upload', $lang['txt_upload'])->id('upload__file')
1681
            ->attrs(['type' => 'file']);
1682
    $form->addTagClose('p');
1683
    $form->addTagOpen('p');
1684
    $form->addTextInput('mediaid', $lang['txt_filename'])->id('upload__name')
1685
            ->val(noNS($id));
1686
    $form->addButton('', $lang['btn_upload'])->attr('type', 'submit');
1687
    $form->addTagClose('p');
1688
    if ($auth >= $auth_ow){
1689
        $form->addTagOpen('p');
1690
        $attrs = array();
1691
        if ($update) $attrs['checked'] = 'checked';
1692
        $form->addCheckbox('ow', $lang['txt_overwrt'])->id('dw__ow')->val('1')
1693
            ->addClass('check')->attrs($attrs);
1694
        $form->addTagClose('p');
1695
    }
1696
    $form->addTagClose('div');
1697
1698
    if (!$fullscreen) {
1699
        echo '<div class="upload">'. $lang['mediaupload'] .'</div>'.DOKU_LF;
1700
    } else {
1701
        echo DOKU_LF;
1702
    }
1703
1704
    echo '<div id="mediamanager__uploader">'.DOKU_LF;
1705
    echo $form->toHTML('Upload');
1706
    echo '</div>'.DOKU_LF;
1707
1708
    echo '<p class="maxsize">';
1709
    printf($lang['maxuploadsize'], filesize_h(media_getuploadsize()));
1710
    echo ' <a class="allowedmime" href="#">'. $lang['allowedmime'] .'</a>';
1711
    echo ' <span>'. implode(', ', array_keys(getMimeTypes())) .'</span>';
1712
    echo '</p>'.DOKU_LF;
1713
}
1714
1715
/**
1716
 * Returns the size uploaded files may have
1717
 *
1718
 * This uses a conservative approach using the lowest number found
1719
 * in any of the limiting ini settings
1720
 *
1721
 * @returns int size in bytes
1722
 */
1723
function media_getuploadsize(){
1724
    $okay = 0;
1725
1726
    $post = (int) php_to_byte(@ini_get('post_max_size'));
1727
    $suho = (int) php_to_byte(@ini_get('suhosin.post.max_value_length'));
1728
    $upld = (int) php_to_byte(@ini_get('upload_max_filesize'));
1729
1730
    if($post && ($post < $okay || $okay == 0)) $okay = $post;
1731
    if($suho && ($suho < $okay || $okay == 0)) $okay = $suho;
1732
    if($upld && ($upld < $okay || $okay == 0)) $okay = $upld;
1733
1734
    return $okay;
1735
}
1736
1737
/**
1738
 * Print the search field form
1739
 *
1740
 * @author Tobias Sarnowski <[email protected]>
1741
 * @author Kate Arzamastseva <[email protected]>
1742
 *
1743
 * @param string $ns
1744
 * @param string $query
1745
 * @param bool $fullscreen
1746
 */
1747
function media_searchform($ns, $query = '', $fullscreen = false) {
1748
    global $lang;
1749
1750
    // The default HTML search form
1751
    $form = new Form([
1752
        'id'     => 'dw__mediasearch',
1753
        'action' => ($fullscreen)
1754
                    ? media_managerURL([], '&')
1755
                    : DOKU_BASE.'lib/exe/mediamanager.php',
1756
    ]);
1757
    $form->addTagOpen('div')->addClass('no');
1758
    $form->setHiddenField('ns', $ns);
1759
    $form->setHiddenField($fullscreen ? 'mediado' : 'do', 'searchlist');
1760
1761
    $form->addTagOpen('p');
1762
    $form->addTextInput('q', $lang['searchmedia'])
1763
            ->attr('title', sprintf($lang['searchmedia_in'], hsc($ns) .':*'))
1764
            ->val($query);
1765
    $form->addHTML(' ');
1766
    $form->addButton('', $lang['btn_search'])->attr('type', 'submit');
1767
    $form->addTagClose('p');
1768
    $form->addTagClose('div');
1769
    print $form->toHTML('SearchMedia');
1770
}
1771
1772
/**
1773
 * Build a tree outline of available media namespaces
1774
 *
1775
 * @author Andreas Gohr <[email protected]>
1776
 *
1777
 * @param string $ns
1778
 */
1779
function media_nstree($ns){
1780
    global $conf;
1781
    global $lang;
1782
1783
    // currently selected namespace
1784
    $ns  = cleanID($ns);
1785
    if(empty($ns)){
1786
        global $ID;
1787
        $ns = (string)getNS($ID);
1788
    }
1789
1790
    $ns_dir  = utf8_encodeFN(str_replace(':','/',$ns));
1791
1792
    $data = array();
1793
    search($data,$conf['mediadir'],'search_index',array('ns' => $ns_dir, 'nofiles' => true));
1794
1795
    // wrap a list with the root level around the other namespaces
1796
    array_unshift($data, array('level' => 0, 'id' => '', 'open' =>'true',
1797
                               'label' => '['.$lang['mediaroot'].']'));
1798
1799
    // insert the current ns into the hierarchy if it isn't already part of it
1800
    $ns_parts = explode(':', $ns);
1801
    $tmp_ns = '';
1802
    $pos = 0;
1803
    foreach ($ns_parts as $level => $part) {
1804
        if ($tmp_ns) $tmp_ns .= ':'.$part;
1805
        else $tmp_ns = $part;
1806
1807
        // find the namespace parts or insert them
1808
        while ($data[$pos]['id'] != $tmp_ns) {
1809
            if (
1810
                $pos >= count($data) ||
1811
                ($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
1812
            ) {
1813
                array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true')));
1814
                break;
1815
            }
1816
            ++$pos;
1817
        }
1818
    }
1819
1820
    echo html_buildlist($data,'idx','media_nstree_item','media_nstree_li');
1821
}
1822
1823
/**
1824
 * Userfunction for html_buildlist
1825
 *
1826
 * Prints a media namespace tree item
1827
 *
1828
 * @author Andreas Gohr <[email protected]>
1829
 *
1830
 * @param array $item
1831
 * @return string html
1832
 */
1833
function media_nstree_item($item){
1834
    global $INPUT;
1835
    $pos   = strrpos($item['id'], ':');
1836
    $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0);
1837
    if(empty($item['label'])) $item['label'] = $label;
1838
1839
    $ret  = '';
1840
    if (!($INPUT->str('do') == 'media'))
1841
    $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
1842
    else $ret .= '<a href="'.media_managerURL(['ns' => idfilter($item['id'], false), 'tab_files' => 'files'])
1843
        .'" class="idx_dir">';
1844
    $ret .= $item['label'];
1845
    $ret .= '</a>';
1846
    return $ret;
1847
}
1848
1849
/**
1850
 * Userfunction for html_buildlist
1851
 *
1852
 * Prints a media namespace tree item opener
1853
 *
1854
 * @author Andreas Gohr <[email protected]>
1855
 *
1856
 * @param array $item
1857
 * @return string html
1858
 */
1859
function media_nstree_li($item){
1860
    $class='media level'.$item['level'];
1861
    if($item['open']){
1862
        $class .= ' open';
1863
        $img   = DOKU_BASE.'lib/images/minus.gif';
1864
        $alt   = '−';
1865
    }else{
1866
        $class .= ' closed';
1867
        $img   = DOKU_BASE.'lib/images/plus.gif';
1868
        $alt   = '+';
1869
    }
1870
    // TODO: only deliver an image if it actually has a subtree...
1871
    return '<li class="'.$class.'">'.
1872
        '<img src="'.$img.'" alt="'.$alt.'" />';
1873
}
1874
1875
/**
1876
 * Resizes the given image to the given size
1877
 *
1878
 * @author  Andreas Gohr <[email protected]>
1879
 *
1880
 * @param string $file filename, path to file
1881
 * @param string $ext  extension
1882
 * @param int    $w    desired width
1883
 * @param int    $h    desired height
1884
 * @return string path to resized or original size if failed
1885
 */
1886
function media_resize_image($file, $ext, $w, $h=0){
1887
    global $conf;
1888
    if(!$h) $h = $w;
1889
    // we wont scale up to infinity
1890
    if($w > 2000 || $h > 2000) return $file;
1891
1892
    //cache
1893
    $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
1894
    $mtime = (int) @filemtime($local); // 0 if not exists
1895
1896
    $options = [
1897
        'quality' => $conf['jpg_quality'],
1898
        'imconvert' => $conf['im_convert'],
1899
    ];
1900
1901
    if( $mtime <= (int) @filemtime($file) ) {
1902
        try {
1903
            \splitbrain\slika\Slika::run($file, $options)
1904
                                   ->autorotate()
1905
                                   ->resize($w, $h)
1906
                                   ->save($local, $ext);
1907
            if($conf['fperm']) @chmod($local, $conf['fperm']);
1908
        } catch (\splitbrain\slika\Exception $e) {
1909
            dbglog($e->getMessage());
1910
            return $file;
1911
        }
1912
    }
1913
1914
    return $local;
1915
}
1916
1917
/**
1918
 * Center crops the given image to the wanted size
1919
 *
1920
 * @author  Andreas Gohr <[email protected]>
1921
 *
1922
 * @param string $file filename, path to file
1923
 * @param string $ext  extension
1924
 * @param int    $w    desired width
1925
 * @param int    $h    desired height
1926
 * @return string path to resized or original size if failed
1927
 */
1928
function media_crop_image($file, $ext, $w, $h=0){
1929
    global $conf;
1930
    if(!$h) $h = $w;
1931
    // we wont scale up to infinity
1932
    if($w > 2000 || $h > 2000) return $file;
1933
1934
    //cache
1935
    $local = getCacheName($file,'.media.'.$w.'x'.$h.'.crop.'.$ext);
1936
    $mtime = (int) @filemtime($local); // 0 if not exists
1937
1938
    $options = [
1939
        'quality' => $conf['jpg_quality'],
1940
        'imconvert' => $conf['im_convert'],
1941
    ];
1942
1943
    if( $mtime <= (int) @filemtime($file) ) {
1944
        try {
1945
            \splitbrain\slika\Slika::run($file, $options)
1946
                                   ->autorotate()
1947
                                    ->crop($w, $h)
1948
                                    ->save($local, $ext);
1949
            if($conf['fperm']) @chmod($local, $conf['fperm']);
1950
        } catch (\splitbrain\slika\Exception $e) {
1951
            dbglog($e->getMessage());
1952
            return $file;
1953
        }
1954
    }
1955
1956
    return $local;
1957
}
1958
1959
/**
1960
 * Calculate a token to be used to verify fetch requests for resized or
1961
 * cropped images have been internally generated - and prevent external
1962
 * DDOS attacks via fetch
1963
 *
1964
 * @author Christopher Smith <[email protected]>
1965
 *
1966
 * @param string  $id    id of the image
1967
 * @param int     $w     resize/crop width
1968
 * @param int     $h     resize/crop height
1969
 * @return string token or empty string if no token required
1970
 */
1971
function media_get_token($id,$w,$h){
1972
    // token is only required for modified images
1973
    if ($w || $h || media_isexternal($id)) {
1974
        $token = $id;
1975
        if ($w) $token .= '.'.$w;
1976
        if ($h) $token .= '.'.$h;
1977
1978
        return substr(\dokuwiki\PassHash::hmac('md5', $token, auth_cookiesalt()),0,6);
1979
    }
1980
1981
    return '';
1982
}
1983
1984
/**
1985
 * Download a remote file and return local filename
1986
 *
1987
 * returns false if download fails. Uses cached file if available and
1988
 * wanted
1989
 *
1990
 * @author  Andreas Gohr <[email protected]>
1991
 * @author  Pavel Vitis <[email protected]>
1992
 *
1993
 * @param string $url
1994
 * @param string $ext   extension
1995
 * @param int    $cache cachetime in seconds
1996
 * @return false|string path to cached file
1997
 */
1998
function media_get_from_URL($url,$ext,$cache){
1999
    global $conf;
2000
2001
    // if no cache or fetchsize just redirect
2002
    if ($cache==0)           return false;
2003
    if (!$conf['fetchsize']) return false;
2004
2005
    $local = getCacheName(strtolower($url),".media.$ext");
2006
    $mtime = @filemtime($local); // 0 if not exists
2007
2008
    //decide if download needed:
2009
    if(($mtime == 0) || // cache does not exist
2010
        ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired
2011
    ) {
2012
        if(media_image_download($url, $local)) {
2013
            return $local;
2014
        } else {
2015
            return false;
2016
        }
2017
    }
2018
2019
    //if cache exists use it else
2020
    if($mtime) return $local;
2021
2022
    //else return false
2023
    return false;
2024
}
2025
2026
/**
2027
 * Download image files
2028
 *
2029
 * @author Andreas Gohr <[email protected]>
2030
 *
2031
 * @param string $url
2032
 * @param string $file path to file in which to put the downloaded content
2033
 * @return bool
2034
 */
2035
function media_image_download($url,$file){
2036
    global $conf;
2037
    $http = new DokuHTTPClient();
2038
    $http->keep_alive = false; // we do single ops here, no need for keep-alive
2039
2040
    $http->max_bodysize = $conf['fetchsize'];
2041
    $http->timeout = 25; //max. 25 sec
2042
    $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
2043
2044
    $data = $http->get($url);
2045
    if(!$data) return false;
2046
2047
    $fileexists = file_exists($file);
2048
    $fp = @fopen($file,"w");
2049
    if(!$fp) return false;
2050
    fwrite($fp,$data);
2051
    fclose($fp);
2052
    if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
2053
2054
    // check if it is really an image
2055
    $info = @getimagesize($file);
2056
    if(!$info){
2057
        @unlink($file);
2058
        return false;
2059
    }
2060
2061
    return true;
2062
}
2063
2064
/**
2065
 * resize images using external ImageMagick convert program
2066
 *
2067
 * @author Pavel Vitis <[email protected]>
2068
 * @author Andreas Gohr <[email protected]>
2069
 *
2070
 * @param string $ext     extension
2071
 * @param string $from    filename path to file
2072
 * @param int    $from_w  original width
2073
 * @param int    $from_h  original height
2074
 * @param string $to      path to resized file
2075
 * @param int    $to_w    desired width
2076
 * @param int    $to_h    desired height
2077
 * @return bool
2078
 */
2079
function media_resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
2080
    global $conf;
2081
2082
    // check if convert is configured
2083
    if(!$conf['im_convert']) return false;
2084
2085
    // prepare command
2086
    $cmd  = $conf['im_convert'];
2087
    $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
2088
    if ($ext == 'jpg' || $ext == 'jpeg') {
2089
        $cmd .= ' -quality '.$conf['jpg_quality'];
2090
    }
2091
    $cmd .= " $from $to";
2092
2093
    @exec($cmd,$out,$retval);
2094
    if ($retval == 0) return true;
2095
    return false;
2096
}
2097
2098
/**
2099
 * crop images using external ImageMagick convert program
2100
 *
2101
 * @author Andreas Gohr <[email protected]>
2102
 *
2103
 * @param string $ext     extension
2104
 * @param string $from    filename path to file
2105
 * @param int    $from_w  original width
2106
 * @param int    $from_h  original height
2107
 * @param string $to      path to resized file
2108
 * @param int    $to_w    desired width
2109
 * @param int    $to_h    desired height
2110
 * @param int    $ofs_x   offset of crop centre
2111
 * @param int    $ofs_y   offset of crop centre
2112
 * @return bool
2113
 * @deprecated 2020-09-01
2114
 */
2115
function media_crop_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x,$ofs_y){
2116
    global $conf;
2117
    dbg_deprecated('splitbrain\\Slika');
2118
2119
    // check if convert is configured
2120
    if(!$conf['im_convert']) return false;
2121
2122
    // prepare command
2123
    $cmd  = $conf['im_convert'];
2124
    $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y;
2125
    if ($ext == 'jpg' || $ext == 'jpeg') {
2126
        $cmd .= ' -quality '.$conf['jpg_quality'];
2127
    }
2128
    $cmd .= " $from $to";
2129
2130
    @exec($cmd,$out,$retval);
2131
    if ($retval == 0) return true;
2132
    return false;
2133
}
2134
2135
/**
2136
 * resize or crop images using PHP's libGD support
2137
 *
2138
 * @author Andreas Gohr <[email protected]>
2139
 * @author Sebastian Wienecke <[email protected]>
2140
 *
2141
 * @param string $ext     extension
2142
 * @param string $from    filename path to file
2143
 * @param int    $from_w  original width
2144
 * @param int    $from_h  original height
2145
 * @param string $to      path to resized file
2146
 * @param int    $to_w    desired width
2147
 * @param int    $to_h    desired height
2148
 * @param int    $ofs_x   offset of crop centre
2149
 * @param int    $ofs_y   offset of crop centre
2150
 * @return bool
2151
 * @deprecated 2020-09-01
2152
 */
2153
function media_resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x=0,$ofs_y=0){
2154
    global $conf;
2155
    dbg_deprecated('splitbrain\\Slika');
2156
2157
    if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
2158
2159
    // check available memory
2160
    if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
2161
        return false;
2162
    }
2163
2164
    // create an image of the given filetype
2165
    $image = false;
2166
    if ($ext == 'jpg' || $ext == 'jpeg'){
2167
        if(!function_exists("imagecreatefromjpeg")) return false;
2168
        $image = @imagecreatefromjpeg($from);
2169
    }elseif($ext == 'png') {
2170
        if(!function_exists("imagecreatefrompng")) return false;
2171
        $image = @imagecreatefrompng($from);
2172
2173
    }elseif($ext == 'gif') {
2174
        if(!function_exists("imagecreatefromgif")) return false;
2175
        $image = @imagecreatefromgif($from);
2176
    }
2177
    if(!$image) return false;
2178
2179
    $newimg = false;
2180
    if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){
2181
        $newimg = @imagecreatetruecolor ($to_w, $to_h);
2182
    }
2183
    if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
2184
    if(!$newimg){
2185
        imagedestroy($image);
2186
        return false;
2187
    }
2188
2189
    //keep png alpha channel if possible
2190
    if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
2191
        imagealphablending($newimg, false);
2192
        imagesavealpha($newimg,true);
2193
    }
2194
2195
    //keep gif transparent color if possible
2196
    if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
2197
        if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
2198
            $transcolorindex = @imagecolortransparent($image);
2199
            if($transcolorindex >= 0 ) { //transparent color exists
2200
                $transcolor = @imagecolorsforindex($image, $transcolorindex);
2201
                $transcolorindex = @imagecolorallocate(
2202
                    $newimg,
2203
                    $transcolor['red'],
2204
                    $transcolor['green'],
2205
                    $transcolor['blue']
2206
                );
2207
                @imagefill($newimg, 0, 0, $transcolorindex);
2208
                @imagecolortransparent($newimg, $transcolorindex);
2209
            }else{ //filling with white
2210
                $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2211
                @imagefill($newimg, 0, 0, $whitecolorindex);
2212
            }
2213
        }else{ //filling with white
2214
            $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2215
            @imagefill($newimg, 0, 0, $whitecolorindex);
2216
        }
2217
    }
2218
2219
    //try resampling first
2220
    if(function_exists("imagecopyresampled")){
2221
        if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) {
2222
            imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2223
        }
2224
    }else{
2225
        imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2226
    }
2227
2228
    $okay = false;
2229
    if ($ext == 'jpg' || $ext == 'jpeg'){
2230
        if(!function_exists('imagejpeg')){
2231
            $okay = false;
2232
        }else{
2233
            $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
2234
        }
2235
    }elseif($ext == 'png') {
2236
        if(!function_exists('imagepng')){
2237
            $okay = false;
2238
        }else{
2239
            $okay =  imagepng($newimg, $to);
2240
        }
2241
    }elseif($ext == 'gif') {
2242
        if(!function_exists('imagegif')){
2243
            $okay = false;
2244
        }else{
2245
            $okay = imagegif($newimg, $to);
2246
        }
2247
    }
2248
2249
    // destroy GD image ressources
2250
    if($image) imagedestroy($image);
2251
    if($newimg) imagedestroy($newimg);
2252
2253
    return $okay;
2254
}
2255
2256
/**
2257
 * Return other media files with the same base name
2258
 * but different extensions.
2259
 *
2260
 * @param string   $src     - ID of media file
2261
 * @param string[] $exts    - alternative extensions to find other files for
2262
 * @return array            - array(mime type => file ID)
2263
 *
2264
 * @author Anika Henke <[email protected]>
2265
 */
2266
function media_alternativefiles($src, $exts){
2267
2268
    $files = array();
2269
    list($srcExt, /* $srcMime */) = mimetype($src);
2270
    $filebase = substr($src, 0, -1 * (strlen($srcExt)+1));
2271
2272
    foreach($exts as $ext) {
2273
        $fileid = $filebase.'.'.$ext;
2274
        $file = mediaFN($fileid);
2275
        if(file_exists($file)) {
2276
            list(/* $fileExt */, $fileMime) = mimetype($file);
2277
            $files[$fileMime] = $fileid;
2278
        }
2279
    }
2280
    return $files;
2281
}
2282
2283
/**
2284
 * Check if video/audio is supported to be embedded.
2285
 *
2286
 * @param string $mime      - mimetype of media file
2287
 * @param string $type      - type of media files to check ('video', 'audio', or null for all)
2288
 * @return boolean
2289
 *
2290
 * @author Anika Henke <[email protected]>
2291
 */
2292
function media_supportedav($mime, $type=NULL){
2293
    $supportedAudio = array(
2294
        'ogg' => 'audio/ogg',
2295
        'mp3' => 'audio/mpeg',
2296
        'wav' => 'audio/wav',
2297
    );
2298
    $supportedVideo = array(
2299
        'webm' => 'video/webm',
2300
        'ogv' => 'video/ogg',
2301
        'mp4' => 'video/mp4',
2302
    );
2303
    if ($type == 'audio') {
2304
        $supportedAv = $supportedAudio;
2305
    } elseif ($type == 'video') {
2306
        $supportedAv = $supportedVideo;
2307
    } else {
2308
        $supportedAv = array_merge($supportedAudio, $supportedVideo);
2309
    }
2310
    return in_array($mime, $supportedAv);
2311
}
2312
2313
/**
2314
 * Return track media files with the same base name
2315
 * but extensions that indicate kind and lang.
2316
 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt...
2317
 *
2318
 * @param string   $src     - ID of media file
2319
 * @return array            - array(mediaID => array( kind, srclang ))
2320
 *
2321
 * @author Schplurtz le Déboulonné <[email protected]>
2322
 */
2323
function media_trackfiles($src){
2324
    $kinds=array(
2325
        'sub' => 'subtitles',
2326
        'cap' => 'captions',
2327
        'des' => 'descriptions',
2328
        'cha' => 'chapters',
2329
        'met' => 'metadata'
2330
    );
2331
2332
    $files = array();
2333
    $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/';
2334
    $baseid=pathinfo($src, PATHINFO_FILENAME);
2335
    $pattern=mediaFN($baseid).'.*.*.vtt';
2336
    $list=glob($pattern);
2337
    foreach($list as $track) {
2338
        if(preg_match($re, $track, $matches)){
2339
            $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=array(
2340
                $kinds[$matches[1]],
2341
                $matches[2],
2342
            );
2343
        }
2344
    }
2345
    return $files;
2346
}
2347
2348
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
2349