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