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