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