Merge pull request #3115 from moisesbr-dw/sort-with-collator
[dokuwiki.git] / inc / media.php
blob2f0a476ff5ae09514229c61909de26a0b6b70993
1 <?php
2 /**
3 * All output and handler function needed for the media management popup
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
9 use dokuwiki\ChangeLog\MediaChangeLog;
10 use dokuwiki\HTTP\DokuHTTPClient;
11 use dokuwiki\Subscriptions\MediaSubscriptionSender;
12 use dokuwiki\Extension\Event;
13 use dokuwiki\Utf8\Sort;
15 /**
16 * Lists pages which currently use a media file selected for deletion
18 * References uses the same visual as search results and share
19 * their CSS tags except pagenames won't be links.
21 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
23 * @param array $data
24 * @param string $id
26 function media_filesinuse($data,$id){
27 global $lang;
28 echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>';
29 echo '<p>'.hsc($lang['ref_inuse']).'</p>';
31 $hidden=0; //count of hits without read permission
32 foreach($data as $row){
33 if(auth_quickaclcheck($row) >= AUTH_READ && isVisiblePage($row)){
34 echo '<div class="search_result">';
35 echo '<span class="mediaref_ref">'.hsc($row).'</span>';
36 echo '</div>';
37 }else
38 $hidden++;
40 if ($hidden){
41 print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>';
45 /**
46 * Handles the saving of image meta data
48 * @author Andreas Gohr <andi@splitbrain.org>
49 * @author Kate Arzamastseva <pshns@ukr.net>
51 * @param string $id media id
52 * @param int $auth permission level
53 * @param array $data
54 * @return false|string
56 function media_metasave($id,$auth,$data){
57 if($auth < AUTH_UPLOAD) return false;
58 if(!checkSecurityToken()) return false;
59 global $lang;
60 global $conf;
61 $src = mediaFN($id);
63 $meta = new JpegMeta($src);
64 $meta->_parseAll();
66 foreach($data as $key => $val){
67 $val=trim($val);
68 if(empty($val)){
69 $meta->deleteField($key);
70 }else{
71 $meta->setField($key,$val);
75 $old = @filemtime($src);
76 if(!file_exists(mediaFN($id, $old)) && file_exists($src)) {
77 // add old revision to the attic
78 media_saveOldRevision($id);
80 $filesize_old = filesize($src);
81 if($meta->save()){
82 if($conf['fperm']) chmod($src, $conf['fperm']);
83 @clearstatcache(true, $src);
84 $new = @filemtime($src);
85 $filesize_new = filesize($src);
86 $sizechange = $filesize_new - $filesize_old;
88 // add a log entry to the media changelog
89 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange);
91 msg($lang['metasaveok'],1);
92 return $id;
93 }else{
94 msg($lang['metasaveerr'],-1);
95 return false;
99 /**
100 * check if a media is external source
102 * @author Gerrit Uitslag <klapinklapin@gmail.com>
104 * @param string $id the media ID or URL
105 * @return bool
107 function media_isexternal($id){
108 if (preg_match('#^(?:https?|ftp)://#i', $id)) return true;
109 return false;
113 * Check if a media item is public (eg, external URL or readable by @ALL)
115 * @author Andreas Gohr <andi@splitbrain.org>
117 * @param string $id the media ID or URL
118 * @return bool
120 function media_ispublic($id){
121 if(media_isexternal($id)) return true;
122 $id = cleanID($id);
123 if(auth_aclcheck(getNS($id).':*', '', array()) >= AUTH_READ) return true;
124 return false;
128 * Display the form to edit image meta data
130 * @author Andreas Gohr <andi@splitbrain.org>
131 * @author Kate Arzamastseva <pshns@ukr.net>
133 * @param string $id media id
134 * @param int $auth permission level
135 * @return bool
137 function media_metaform($id,$auth){
138 global $lang;
140 if($auth < AUTH_UPLOAD) {
141 echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
142 return false;
145 // load the field descriptions
146 static $fields = null;
147 if(is_null($fields)){
148 $config_files = getConfigFiles('mediameta');
149 foreach ($config_files as $config_file) {
150 if(file_exists($config_file)) include($config_file);
154 $src = mediaFN($id);
156 // output
157 $form = new Doku_Form(array('action' => media_managerURL(array('tab_details' => 'view'), '&'),
158 'class' => 'meta'));
159 $form->addHidden('img', $id);
160 $form->addHidden('mediado', 'save');
161 foreach($fields as $key => $field){
162 // get current value
163 if (empty($field[0])) continue;
164 $tags = array($field[0]);
165 if(is_array($field[3])) $tags = array_merge($tags,$field[3]);
166 $value = tpl_img_getTag($tags,'',$src);
167 $value = cleanText($value);
169 // prepare attributes
170 $p = array();
171 $p['class'] = 'edit';
172 $p['id'] = 'meta__'.$key;
173 $p['name'] = 'meta['.$field[0].']';
174 $p_attrs = array('class' => 'edit');
176 $form->addElement('<div class="row">');
177 if($field[2] == 'text'){
178 $form->addElement(
179 form_makeField(
180 'text',
181 $p['name'],
182 $value,
183 ($lang[$field[1]]) ? $lang[$field[1]] : $field[1] . ':',
184 $p['id'],
185 $p['class'],
186 $p_attrs
189 }else{
190 $att = buildAttributes($p);
191 $form->addElement('<label for="meta__'.$key.'">'.$lang[$field[1]].'</label>');
192 $form->addElement("<textarea $att rows=\"6\" cols=\"50\">".formText($value).'</textarea>');
194 $form->addElement('</div>'.NL);
196 $form->addElement('<div class="buttons">');
197 $form->addElement(
198 form_makeButton(
199 'submit',
201 $lang['btn_save'],
202 array('accesskey' => 's', 'name' => 'mediado[save]')
205 $form->addElement('</div>'.NL);
206 $form->printForm();
208 return true;
212 * Convenience function to check if a media file is still in use
214 * @author Michael Klier <chi@chimeric.de>
216 * @param string $id media id
217 * @return array|bool
219 function media_inuse($id) {
220 global $conf;
222 if($conf['refcheck']){
223 $mediareferences = ft_mediause($id,true);
224 if(!count($mediareferences)) {
225 return false;
226 } else {
227 return $mediareferences;
229 } else {
230 return false;
235 * Handles media file deletions
237 * If configured, checks for media references before deletion
239 * @author Andreas Gohr <andi@splitbrain.org>
241 * @param string $id media id
242 * @param int $auth no longer used
243 * @return int One of: 0,
244 * DOKU_MEDIA_DELETED,
245 * DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS,
246 * DOKU_MEDIA_NOT_AUTH,
247 * DOKU_MEDIA_INUSE
249 function media_delete($id,$auth){
250 global $lang;
251 $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':'));
252 if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH;
253 if(media_inuse($id)) return DOKU_MEDIA_INUSE;
255 $file = mediaFN($id);
257 // trigger an event - MEDIA_DELETE_FILE
258 $data = array();
259 $data['id'] = $id;
260 $data['name'] = \dokuwiki\Utf8\PhpString::basename($file);
261 $data['path'] = $file;
262 $data['size'] = (file_exists($file)) ? filesize($file) : 0;
264 $data['unl'] = false;
265 $data['del'] = false;
266 $evt = new Event('MEDIA_DELETE_FILE',$data);
267 if ($evt->advise_before()) {
268 $old = @filemtime($file);
269 if(!file_exists(mediaFN($id, $old)) && file_exists($file)) {
270 // add old revision to the attic
271 media_saveOldRevision($id);
274 $data['unl'] = @unlink($file);
275 if($data['unl']) {
276 $sizechange = 0 - $data['size'];
277 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange);
279 $data['del'] = io_sweepNS($id, 'mediadir');
282 $evt->advise_after();
283 unset($evt);
285 if($data['unl'] && $data['del']){
286 return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS;
289 return $data['unl'] ? DOKU_MEDIA_DELETED : 0;
293 * Handle file uploads via XMLHttpRequest
295 * @param string $ns target namespace
296 * @param int $auth current auth check result
297 * @return false|string false on error, id of the new file on success
299 function media_upload_xhr($ns,$auth){
300 if(!checkSecurityToken()) return false;
301 global $INPUT;
303 $id = $INPUT->get->str('qqfile');
304 list($ext,$mime) = mimetype($id);
305 $input = fopen("php://input", "r");
306 if (!($tmp = io_mktmpdir())) return false;
307 $path = $tmp.'/'.md5($id);
308 $target = fopen($path, "w");
309 $realSize = stream_copy_to_stream($input, $target);
310 fclose($target);
311 fclose($input);
312 if (isset($_SERVER["CONTENT_LENGTH"]) && ($realSize != (int)$_SERVER["CONTENT_LENGTH"])){
313 unlink($path);
314 return false;
317 $res = media_save(
318 array('name' => $path,
319 'mime' => $mime,
320 'ext' => $ext),
321 $ns.':'.$id,
322 (($INPUT->get->str('ow') == 'true') ? true : false),
323 $auth,
324 'copy'
326 unlink($path);
327 if ($tmp) io_rmdir($tmp, true);
328 if (is_array($res)) {
329 msg($res[0], $res[1]);
330 return false;
332 return $res;
336 * Handles media file uploads
338 * @author Andreas Gohr <andi@splitbrain.org>
339 * @author Michael Klier <chi@chimeric.de>
341 * @param string $ns target namespace
342 * @param int $auth current auth check result
343 * @param bool|array $file $_FILES member, $_FILES['upload'] if false
344 * @return false|string false on error, id of the new file on success
346 function media_upload($ns,$auth,$file=false){
347 if(!checkSecurityToken()) return false;
348 global $lang;
349 global $INPUT;
351 // get file and id
352 $id = $INPUT->post->str('mediaid');
353 if (!$file) $file = $_FILES['upload'];
354 if(empty($id)) $id = $file['name'];
356 // check for errors (messages are done in lib/exe/mediamanager.php)
357 if($file['error']) return false;
359 // check extensions
360 list($fext,$fmime) = mimetype($file['name']);
361 list($iext,$imime) = mimetype($id);
362 if($fext && !$iext){
363 // no extension specified in id - read original one
364 $id .= '.'.$fext;
365 $imime = $fmime;
366 }elseif($fext && $fext != $iext){
367 // extension was changed, print warning
368 msg(sprintf($lang['mediaextchange'],$fext,$iext));
371 $res = media_save(array('name' => $file['tmp_name'],
372 'mime' => $imime,
373 'ext' => $iext), $ns.':'.$id,
374 $INPUT->post->bool('ow'), $auth, 'copy_uploaded_file');
375 if (is_array($res)) {
376 msg($res[0], $res[1]);
377 return false;
379 return $res;
383 * An alternative to move_uploaded_file that copies
385 * Using copy, makes sure any setgid bits on the media directory are honored
387 * @see move_uploaded_file()
389 * @param string $from
390 * @param string $to
391 * @return bool
393 function copy_uploaded_file($from, $to){
394 if(!is_uploaded_file($from)) return false;
395 $ok = copy($from, $to);
396 @unlink($from);
397 return $ok;
401 * This generates an action event and delegates to _media_upload_action().
402 * Action plugins are allowed to pre/postprocess the uploaded file.
403 * (The triggered event is preventable.)
405 * Event data:
406 * $data[0] fn_tmp: the temporary file name (read from $_FILES)
407 * $data[1] fn: the file name of the uploaded file
408 * $data[2] id: the future directory id of the uploaded file
409 * $data[3] imime: the mimetype of the uploaded file
410 * $data[4] overwrite: if an existing file is going to be overwritten
411 * $data[5] move: name of function that performs move/copy/..
413 * @triggers MEDIA_UPLOAD_FINISH
415 * @param array $file
416 * @param string $id media id
417 * @param bool $ow overwrite?
418 * @param int $auth permission level
419 * @param string $move name of functions that performs move/copy/..
420 * @return false|array|string
422 function media_save($file, $id, $ow, $auth, $move) {
423 if($auth < AUTH_UPLOAD) {
424 return array("You don't have permissions to upload files.", -1);
427 if (!isset($file['mime']) || !isset($file['ext'])) {
428 list($ext, $mime) = mimetype($id);
429 if (!isset($file['mime'])) {
430 $file['mime'] = $mime;
432 if (!isset($file['ext'])) {
433 $file['ext'] = $ext;
437 global $lang, $conf;
439 // get filename
440 $id = cleanID($id);
441 $fn = mediaFN($id);
443 // get filetype regexp
444 $types = array_keys(getMimeTypes());
445 $types = array_map(
446 function ($q) {
447 return preg_quote($q, "/");
449 $types
451 $regex = join('|',$types);
453 // because a temp file was created already
454 if(!preg_match('/\.('.$regex.')$/i',$fn)) {
455 return array($lang['uploadwrong'],-1);
458 //check for overwrite
459 $overwrite = file_exists($fn);
460 $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
461 if($overwrite && (!$ow || $auth < $auth_ow)) {
462 return array($lang['uploadexist'], 0);
464 // check for valid content
465 $ok = media_contentcheck($file['name'], $file['mime']);
466 if($ok == -1){
467 return array(sprintf($lang['uploadbadcontent'],'.' . $file['ext']),-1);
468 }elseif($ok == -2){
469 return array($lang['uploadspam'],-1);
470 }elseif($ok == -3){
471 return array($lang['uploadxss'],-1);
474 // prepare event data
475 $data = array();
476 $data[0] = $file['name'];
477 $data[1] = $fn;
478 $data[2] = $id;
479 $data[3] = $file['mime'];
480 $data[4] = $overwrite;
481 $data[5] = $move;
483 // trigger event
484 return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true);
488 * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH
490 * @author Michael Klier <chi@chimeric.de>
492 * @param array $data event data
493 * @return false|array|string
495 function _media_upload_action($data) {
496 // fixme do further sanity tests of given data?
497 if(is_array($data) && count($data)===6) {
498 return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
499 } else {
500 return false; //callback error
505 * Saves an uploaded media file
507 * @author Andreas Gohr <andi@splitbrain.org>
508 * @author Michael Klier <chi@chimeric.de>
509 * @author Kate Arzamastseva <pshns@ukr.net>
511 * @param string $fn_tmp
512 * @param string $fn
513 * @param string $id media id
514 * @param string $imime mime type
515 * @param bool $overwrite overwrite existing?
516 * @param string $move function name
517 * @return array|string
519 function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') {
520 global $conf;
521 global $lang;
522 global $REV;
524 $old = @filemtime($fn);
525 if(!file_exists(mediaFN($id, $old)) && file_exists($fn)) {
526 // add old revision to the attic if missing
527 media_saveOldRevision($id);
530 // prepare directory
531 io_createNamespace($id, 'media');
533 $filesize_old = file_exists($fn) ? filesize($fn) : 0;
535 if($move($fn_tmp, $fn)) {
536 @clearstatcache(true,$fn);
537 $new = @filemtime($fn);
538 // Set the correct permission here.
539 // Always chmod media because they may be saved with different permissions than expected from the php umask.
540 // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
541 chmod($fn, $conf['fmode']);
542 msg($lang['uploadsucc'],1);
543 media_notify($id,$fn,$imime,$old,$new);
544 // add a log entry to the media changelog
545 $filesize_new = filesize($fn);
546 $sizechange = $filesize_new - $filesize_old;
547 if($REV) {
548 addMediaLogEntry(
549 $new,
550 $id,
551 DOKU_CHANGE_TYPE_REVERT,
552 sprintf($lang['restored'], dformat($REV)),
553 $REV,
554 null,
555 $sizechange
557 } elseif($overwrite) {
558 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
559 } else {
560 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
562 return $id;
563 }else{
564 return array($lang['uploadfail'],-1);
569 * Moves the current version of media file to the media_attic
570 * directory
572 * @author Kate Arzamastseva <pshns@ukr.net>
574 * @param string $id
575 * @return int - revision date
577 function media_saveOldRevision($id){
578 global $conf, $lang;
580 $oldf = mediaFN($id);
581 if(!file_exists($oldf)) return '';
582 $date = filemtime($oldf);
583 if (!$conf['mediarevisions']) return $date;
585 $medialog = new MediaChangeLog($id);
586 if (!$medialog->getRevisionInfo($date)) {
587 // there was an external edit,
588 // there is no log entry for current version of file
589 $sizechange = filesize($oldf);
590 if(!file_exists(mediaMetaFN($id, '.changes'))) {
591 addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange);
592 } else {
593 $oldRev = $medialog->getRevisions(-1, 1); // from changelog
594 $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]);
595 $filesize_old = filesize(mediaFN($id, $oldRev));
596 $sizechange = $sizechange - $filesize_old;
598 addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange);
602 $newf = mediaFN($id,$date);
603 io_makeFileDir($newf);
604 if(copy($oldf, $newf)) {
605 // Set the correct permission here.
606 // Always chmod media because they may be saved with different permissions than expected from the php umask.
607 // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
608 chmod($newf, $conf['fmode']);
610 return $date;
614 * This function checks if the uploaded content is really what the
615 * mimetype says it is. We also do spam checking for text types here.
617 * We need to do this stuff because we can not rely on the browser
618 * to do this check correctly. Yes, IE is broken as usual.
620 * @author Andreas Gohr <andi@splitbrain.org>
621 * @link http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting
622 * @fixme check all 26 magic IE filetypes here?
624 * @param string $file path to file
625 * @param string $mime mimetype
626 * @return int
628 function media_contentcheck($file,$mime){
629 global $conf;
630 if($conf['iexssprotect']){
631 $fh = @fopen($file, 'rb');
632 if($fh){
633 $bytes = fread($fh, 256);
634 fclose($fh);
635 if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i',$bytes)){
636 return -3; //XSS: possibly malicious content
640 if(substr($mime,0,6) == 'image/'){
641 $info = @getimagesize($file);
642 if($mime == 'image/gif' && $info[2] != 1){
643 return -1; // uploaded content did not match the file extension
644 }elseif($mime == 'image/jpeg' && $info[2] != 2){
645 return -1;
646 }elseif($mime == 'image/png' && $info[2] != 3){
647 return -1;
649 # fixme maybe check other images types as well
650 }elseif(substr($mime,0,5) == 'text/'){
651 global $TEXT;
652 $TEXT = io_readFile($file);
653 if(checkwordblock()){
654 return -2; //blocked by the spam blacklist
657 return 0;
661 * Send a notify mail on uploads
663 * @author Andreas Gohr <andi@splitbrain.org>
665 * @param string $id media id
666 * @param string $file path to file
667 * @param string $mime mime type
668 * @param bool|int $old_rev revision timestamp or false
669 * @return bool
671 function media_notify($id,$file,$mime,$old_rev=false,$current_rev=false){
672 global $conf;
673 if(empty($conf['notify'])) return false; //notify enabled?
675 $subscription = new MediaSubscriptionSender();
676 return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev);
680 * List all files in a given Media namespace
682 * @param string $ns namespace
683 * @param null|int $auth permission level
684 * @param string $jump id
685 * @param bool $fullscreenview
686 * @param bool|string $sort sorting order, false skips sorting
688 function media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){
689 global $conf;
690 global $lang;
691 $ns = cleanID($ns);
693 // check auth our self if not given (needed for ajax calls)
694 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
696 if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
698 if($auth < AUTH_READ){
699 // FIXME: print permission warning here instead?
700 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
701 }else{
702 if (!$fullscreenview) {
703 media_uploadform($ns, $auth);
704 media_searchform($ns);
707 $dir = utf8_encodeFN(str_replace(':','/',$ns));
708 $data = array();
709 search($data,$conf['mediadir'],'search_media',
710 array('showmsg'=>true,'depth'=>1),$dir,1,$sort);
712 if(!count($data)){
713 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
714 }else {
715 if ($fullscreenview) {
716 echo '<ul class="' . _media_get_list_type() . '">';
718 foreach($data as $item){
719 if (!$fullscreenview) {
720 media_printfile($item,$auth,$jump);
721 } else {
722 media_printfile_thumbs($item,$auth,$jump);
725 if ($fullscreenview) echo '</ul>'.NL;
731 * Prints tabs for files list actions
733 * @author Kate Arzamastseva <pshns@ukr.net>
734 * @author Adrian Lang <mail@adrianlang.de>
736 * @param string $selected_tab - opened tab
739 function media_tabs_files($selected_tab = ''){
740 global $lang;
741 $tabs = array();
742 foreach(array('files' => 'mediaselect',
743 'upload' => 'media_uploadtab',
744 'search' => 'media_searchtab') as $tab => $caption) {
745 $tabs[$tab] = array('href' => media_managerURL(array('tab_files' => $tab), '&'),
746 'caption' => $lang[$caption]);
749 html_tabs($tabs, $selected_tab);
753 * Prints tabs for files details actions
755 * @author Kate Arzamastseva <pshns@ukr.net>
756 * @param string $image filename of the current image
757 * @param string $selected_tab opened tab
759 function media_tabs_details($image, $selected_tab = ''){
760 global $lang, $conf;
762 $tabs = array();
763 $tabs['view'] = array('href' => media_managerURL(array('tab_details' => 'view'), '&'),
764 'caption' => $lang['media_viewtab']);
766 list(, $mime) = mimetype($image);
767 if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) {
768 $tabs['edit'] = array('href' => media_managerURL(array('tab_details' => 'edit'), '&'),
769 'caption' => $lang['media_edittab']);
771 if ($conf['mediarevisions']) {
772 $tabs['history'] = array('href' => media_managerURL(array('tab_details' => 'history'), '&'),
773 'caption' => $lang['media_historytab']);
776 html_tabs($tabs, $selected_tab);
780 * Prints options for the tab that displays a list of all files
782 * @author Kate Arzamastseva <pshns@ukr.net>
784 function media_tab_files_options(){
785 global $lang;
786 global $INPUT;
787 global $ID;
788 $form = new Doku_Form(array('class' => 'options', 'method' => 'get',
789 'action' => wl($ID)));
790 $media_manager_params = media_managerURL(array(), '', false, true);
791 foreach($media_manager_params as $pKey => $pVal){
792 $form->addHidden($pKey, $pVal);
794 $form->addHidden('sectok', null);
795 if ($INPUT->has('q')) {
796 $form->addHidden('q', $INPUT->str('q'));
798 $form->addElement('<ul>'.NL);
799 foreach(array('list' => array('listType', array('thumbs', 'rows')),
800 'sort' => array('sortBy', array('name', 'date')))
801 as $group => $content) {
802 $checked = "_media_get_${group}_type";
803 $checked = $checked();
805 $form->addElement('<li class="' . $content[0] . '">');
806 foreach($content[1] as $option) {
807 $attrs = array();
808 if ($checked == $option) {
809 $attrs['checked'] = 'checked';
811 $form->addElement(form_makeRadioField($group . '_dwmedia', $option,
812 $lang['media_' . $group . '_' . $option],
813 $content[0] . '__' . $option,
814 $option, $attrs));
816 $form->addElement('</li>'.NL);
818 $form->addElement('<li>');
819 $form->addElement(form_makeButton('submit', '', $lang['btn_apply']));
820 $form->addElement('</li>'.NL);
821 $form->addElement('</ul>'.NL);
822 $form->printForm();
826 * Returns type of sorting for the list of files in media manager
828 * @author Kate Arzamastseva <pshns@ukr.net>
830 * @return string - sort type
832 function _media_get_sort_type() {
833 return _media_get_display_param('sort', array('default' => 'name', 'date'));
837 * Returns type of listing for the list of files in media manager
839 * @author Kate Arzamastseva <pshns@ukr.net>
841 * @return string - list type
843 function _media_get_list_type() {
844 return _media_get_display_param('list', array('default' => 'thumbs', 'rows'));
848 * Get display parameters
850 * @param string $param name of parameter
851 * @param array $values allowed values, where default value has index key 'default'
852 * @return string the parameter value
854 function _media_get_display_param($param, $values) {
855 global $INPUT;
856 if (in_array($INPUT->str($param), $values)) {
857 // FIXME: Set cookie
858 return $INPUT->str($param);
859 } else {
860 $val = get_doku_pref($param, $values['default']);
861 if (!in_array($val, $values)) {
862 $val = $values['default'];
864 return $val;
869 * Prints tab that displays a list of all files
871 * @author Kate Arzamastseva <pshns@ukr.net>
873 * @param string $ns
874 * @param null|int $auth permission level
875 * @param string $jump item id
877 function media_tab_files($ns,$auth=null,$jump='') {
878 global $lang;
879 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
881 if($auth < AUTH_READ){
882 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
883 }else{
884 media_filelist($ns,$auth,$jump,true,_media_get_sort_type());
889 * Prints tab that displays uploading form
891 * @author Kate Arzamastseva <pshns@ukr.net>
893 * @param string $ns
894 * @param null|int $auth permission level
895 * @param string $jump item id
897 function media_tab_upload($ns,$auth=null,$jump='') {
898 global $lang;
899 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
901 echo '<div class="upload">'.NL;
902 if ($auth >= AUTH_UPLOAD) {
903 echo '<p>' . $lang['mediaupload'] . '</p>';
905 media_uploadform($ns, $auth, true);
906 echo '</div>'.NL;
910 * Prints tab that displays search form
912 * @author Kate Arzamastseva <pshns@ukr.net>
914 * @param string $ns
915 * @param null|int $auth permission level
917 function media_tab_search($ns,$auth=null) {
918 global $INPUT;
920 $do = $INPUT->str('mediado');
921 $query = $INPUT->str('q');
922 echo '<div class="search">'.NL;
924 media_searchform($ns, $query, true);
925 if ($do == 'searchlist' || $query) {
926 media_searchlist($query,$ns,$auth,true,_media_get_sort_type());
928 echo '</div>'.NL;
932 * Prints tab that displays mediafile details
934 * @author Kate Arzamastseva <pshns@ukr.net>
936 * @param string $image media id
937 * @param string $ns
938 * @param null|int $auth permission level
939 * @param string|int $rev revision timestamp or empty string
941 function media_tab_view($image, $ns, $auth=null, $rev='') {
942 global $lang;
943 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
945 if ($image && $auth >= AUTH_READ) {
946 $meta = new JpegMeta(mediaFN($image, $rev));
947 media_preview($image, $auth, $rev, $meta);
948 media_preview_buttons($image, $auth, $rev);
949 media_details($image, $auth, $rev, $meta);
951 } else {
952 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
957 * Prints tab that displays form for editing mediafile metadata
959 * @author Kate Arzamastseva <pshns@ukr.net>
961 * @param string $image media id
962 * @param string $ns
963 * @param null|int $auth permission level
965 function media_tab_edit($image, $ns, $auth=null) {
966 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
968 if ($image) {
969 list(, $mime) = mimetype($image);
970 if ($mime == 'image/jpeg') media_metaform($image,$auth);
975 * Prints tab that displays mediafile revisions
977 * @author Kate Arzamastseva <pshns@ukr.net>
979 * @param string $image media id
980 * @param string $ns
981 * @param null|int $auth permission level
983 function media_tab_history($image, $ns, $auth=null) {
984 global $lang;
985 global $INPUT;
987 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
988 $do = $INPUT->str('mediado');
990 if ($auth >= AUTH_READ && $image) {
991 if ($do == 'diff'){
992 media_diff($image, $ns, $auth);
993 } else {
994 $first = $INPUT->int('first');
995 html_revisions($first, $image);
997 } else {
998 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
1003 * Prints mediafile details
1005 * @param string $image media id
1006 * @param int $auth permission level
1007 * @param int|string $rev revision timestamp or empty string
1008 * @param JpegMeta|bool $meta
1010 * @author Kate Arzamastseva <pshns@ukr.net>
1012 function media_preview($image, $auth, $rev='', $meta=false) {
1014 $size = media_image_preview_size($image, $rev, $meta);
1016 if ($size) {
1017 global $lang;
1018 echo '<div class="image">';
1020 $more = array();
1021 if ($rev) {
1022 $more['rev'] = $rev;
1023 } else {
1024 $t = @filemtime(mediaFN($image));
1025 $more['t'] = $t;
1028 $more['w'] = $size[0];
1029 $more['h'] = $size[1];
1030 $src = ml($image, $more);
1032 echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">';
1033 echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
1034 echo '</a>';
1036 echo '</div>'.NL;
1041 * Prints mediafile action buttons
1043 * @author Kate Arzamastseva <pshns@ukr.net>
1045 * @param string $image media id
1046 * @param int $auth permission level
1047 * @param string|int $rev revision timestamp, or empty string
1049 function media_preview_buttons($image, $auth, $rev='') {
1050 global $lang, $conf;
1052 echo '<ul class="actions">'.NL;
1054 if($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))){
1056 // delete button
1057 $form = new Doku_Form(array('id' => 'mediamanager__btn_delete',
1058 'action'=>media_managerURL(array('delete' => $image), '&')));
1059 $form->addElement(form_makeButton('submit','',$lang['btn_delete']));
1060 echo '<li>';
1061 $form->printForm();
1062 echo '</li>'.NL;
1065 $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1066 if($auth >= $auth_ow && !$rev){
1068 // upload new version button
1069 $form = new Doku_Form(array('id' => 'mediamanager__btn_update',
1070 'action'=>media_managerURL(array('image' => $image, 'mediado' => 'update'), '&')));
1071 $form->addElement(form_makeButton('submit','',$lang['media_update']));
1072 echo '<li>';
1073 $form->printForm();
1074 echo '</li>'.NL;
1077 if($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))){
1079 // restore button
1080 $form = new Doku_Form(array('id' => 'mediamanager__btn_restore',
1081 'action'=>media_managerURL(array('image' => $image), '&')));
1082 $form->addHidden('mediado','restore');
1083 $form->addHidden('rev',$rev);
1084 $form->addElement(form_makeButton('submit','',$lang['media_restore']));
1085 echo '<li>';
1086 $form->printForm();
1087 echo '</li>'.NL;
1090 echo '</ul>'.NL;
1094 * Returns image width and height for mediamanager preview panel
1096 * @author Kate Arzamastseva <pshns@ukr.net>
1097 * @param string $image
1098 * @param int|string $rev
1099 * @param JpegMeta|bool $meta
1100 * @param int $size
1101 * @return array|false
1103 function media_image_preview_size($image, $rev, $meta, $size = 500) {
1104 if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false;
1106 $info = getimagesize(mediaFN($image, $rev));
1107 $w = (int) $info[0];
1108 $h = (int) $info[1];
1110 if($meta && ($w > $size || $h > $size)){
1111 $ratio = $meta->getResizeRatio($size, $size);
1112 $w = floor($w * $ratio);
1113 $h = floor($h * $ratio);
1115 return array($w, $h);
1119 * Returns the requested EXIF/IPTC tag from the image meta
1121 * @author Kate Arzamastseva <pshns@ukr.net>
1123 * @param array $tags array with tags, first existing is returned
1124 * @param JpegMeta $meta
1125 * @param string $alt alternative value
1126 * @return string
1128 function media_getTag($tags,$meta,$alt=''){
1129 if($meta === false) return $alt;
1130 $info = $meta->getField($tags);
1131 if($info == false) return $alt;
1132 return $info;
1136 * Returns mediafile tags
1138 * @author Kate Arzamastseva <pshns@ukr.net>
1140 * @param JpegMeta $meta
1141 * @return array list of tags of the mediafile
1143 function media_file_tags($meta) {
1144 // load the field descriptions
1145 static $fields = null;
1146 if(is_null($fields)){
1147 $config_files = getConfigFiles('mediameta');
1148 foreach ($config_files as $config_file) {
1149 if(file_exists($config_file)) include($config_file);
1153 $tags = array();
1155 foreach($fields as $key => $tag){
1156 $t = array();
1157 if (!empty($tag[0])) $t = array($tag[0]);
1158 if(isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]);
1159 $value = media_getTag($t, $meta);
1160 $tags[] = array('tag' => $tag, 'value' => $value);
1163 return $tags;
1167 * Prints mediafile tags
1169 * @author Kate Arzamastseva <pshns@ukr.net>
1171 * @param string $image image id
1172 * @param int $auth permission level
1173 * @param string|int $rev revision timestamp, or empty string
1174 * @param bool|JpegMeta $meta image object, or create one if false
1176 function media_details($image, $auth, $rev='', $meta=false) {
1177 global $lang;
1179 if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
1180 $tags = media_file_tags($meta);
1182 echo '<dl>'.NL;
1183 foreach($tags as $tag){
1184 if ($tag['value']) {
1185 $value = cleanText($tag['value']);
1186 echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>';
1187 if ($tag['tag'][2] == 'date') echo dformat($value);
1188 else echo hsc($value);
1189 echo '</dd>'.NL;
1192 echo '</dl>'.NL;
1193 echo '<dl>'.NL;
1194 echo '<dt>'.$lang['reference'].':</dt>';
1195 $media_usage = ft_mediause($image,true);
1196 if(count($media_usage) > 0){
1197 foreach($media_usage as $path){
1198 echo '<dd>'.html_wikilink($path).'</dd>';
1200 }else{
1201 echo '<dd>'.$lang['nothingfound'].'</dd>';
1203 echo '</dl>'.NL;
1208 * Shows difference between two revisions of file
1210 * @author Kate Arzamastseva <pshns@ukr.net>
1212 * @param string $image image id
1213 * @param string $ns
1214 * @param int $auth permission level
1215 * @param bool $fromajax
1216 * @return false|null|string
1218 function media_diff($image, $ns, $auth, $fromajax = false) {
1219 global $conf;
1220 global $INPUT;
1222 if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return '';
1224 $rev1 = $INPUT->int('rev');
1226 $rev2 = $INPUT->ref('rev2');
1227 if(is_array($rev2)){
1228 $rev1 = (int) $rev2[0];
1229 $rev2 = (int) $rev2[1];
1231 if(!$rev1){
1232 $rev1 = $rev2;
1233 unset($rev2);
1235 }else{
1236 $rev2 = $INPUT->int('rev2');
1239 if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false;
1240 if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false;
1242 if($rev1 && $rev2){ // two specific revisions wanted
1243 // make sure order is correct (older on the left)
1244 if($rev1 < $rev2){
1245 $l_rev = $rev1;
1246 $r_rev = $rev2;
1247 }else{
1248 $l_rev = $rev2;
1249 $r_rev = $rev1;
1251 }elseif($rev1){ // single revision given, compare to current
1252 $r_rev = '';
1253 $l_rev = $rev1;
1254 }else{ // no revision was given, compare previous to current
1255 $r_rev = '';
1256 $medialog = new MediaChangeLog($image);
1257 $revs = $medialog->getRevisions(0, 1);
1258 if (file_exists(mediaFN($image, $revs[0]))) {
1259 $l_rev = $revs[0];
1260 } else {
1261 $l_rev = '';
1265 // prepare event data
1266 $data = array();
1267 $data[0] = $image;
1268 $data[1] = $l_rev;
1269 $data[2] = $r_rev;
1270 $data[3] = $ns;
1271 $data[4] = $auth;
1272 $data[5] = $fromajax;
1274 // trigger event
1275 return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true);
1279 * Callback for media file diff
1281 * @param array $data event data
1282 * @return false|null
1284 function _media_file_diff($data) {
1285 if(is_array($data) && count($data)===6) {
1286 media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
1287 } else {
1288 return false;
1293 * Shows difference between two revisions of image
1295 * @author Kate Arzamastseva <pshns@ukr.net>
1297 * @param string $image
1298 * @param string|int $l_rev revision timestamp, or empty string
1299 * @param string|int $r_rev revision timestamp, or empty string
1300 * @param string $ns
1301 * @param int $auth permission level
1302 * @param bool $fromajax
1304 function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax){
1305 global $lang;
1306 global $INPUT;
1308 $l_meta = new JpegMeta(mediaFN($image, $l_rev));
1309 $r_meta = new JpegMeta(mediaFN($image, $r_rev));
1311 $is_img = preg_match('/\.(jpe?g|gif|png)$/', $image);
1312 if ($is_img) {
1313 $l_size = media_image_preview_size($image, $l_rev, $l_meta);
1314 $r_size = media_image_preview_size($image, $r_rev, $r_meta);
1315 $is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30));
1317 $difftype = $INPUT->str('difftype');
1319 if (!$fromajax) {
1320 $form = new Doku_Form(array(
1321 'action' => media_managerURL(array(), '&'),
1322 'method' => 'get',
1323 'id' => 'mediamanager__form_diffview',
1324 'class' => 'diffView'
1326 $form->addHidden('sectok', null);
1327 $form->addElement('<input type="hidden" name="rev2[]" value="'.$l_rev.'" ></input>');
1328 $form->addElement('<input type="hidden" name="rev2[]" value="'.$r_rev.'" ></input>');
1329 $form->addHidden('mediado', 'diff');
1330 $form->printForm();
1332 echo NL.'<div id="mediamanager__diff" >'.NL;
1335 if ($difftype == 'opacity' || $difftype == 'portions') {
1336 media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype);
1337 if (!$fromajax) echo '</div>';
1338 return;
1342 list($l_head, $r_head) = html_diff_head($l_rev, $r_rev, $image, true);
1345 <div class="table">
1346 <table>
1347 <tr>
1348 <th><?php echo $l_head; ?></th>
1349 <th><?php echo $r_head; ?></th>
1350 </tr>
1351 <?php
1353 echo '<tr class="image">';
1354 echo '<td>';
1355 media_preview($image, $auth, $l_rev, $l_meta);
1356 echo '</td>';
1358 echo '<td>';
1359 media_preview($image, $auth, $r_rev, $r_meta);
1360 echo '</td>';
1361 echo '</tr>'.NL;
1363 echo '<tr class="actions">';
1364 echo '<td>';
1365 media_preview_buttons($image, $auth, $l_rev);
1366 echo '</td>';
1368 echo '<td>';
1369 media_preview_buttons($image, $auth, $r_rev);
1370 echo '</td>';
1371 echo '</tr>'.NL;
1373 $l_tags = media_file_tags($l_meta);
1374 $r_tags = media_file_tags($r_meta);
1375 // FIXME r_tags-only stuff
1376 foreach ($l_tags as $key => $l_tag) {
1377 if ($l_tag['value'] != $r_tags[$key]['value']) {
1378 $r_tags[$key]['highlighted'] = true;
1379 $l_tags[$key]['highlighted'] = true;
1380 } else if (!$l_tag['value'] || !$r_tags[$key]['value']) {
1381 unset($r_tags[$key]);
1382 unset($l_tags[$key]);
1386 echo '<tr>';
1387 foreach(array($l_tags,$r_tags) as $tags){
1388 echo '<td>'.NL;
1390 echo '<dl class="img_tags">';
1391 foreach($tags as $tag){
1392 $value = cleanText($tag['value']);
1393 if (!$value) $value = '-';
1394 echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
1395 echo '<dd>';
1396 if ($tag['highlighted']) {
1397 echo '<strong>';
1399 if ($tag['tag'][2] == 'date') echo dformat($value);
1400 else echo hsc($value);
1401 if ($tag['highlighted']) {
1402 echo '</strong>';
1404 echo '</dd>';
1406 echo '</dl>'.NL;
1408 echo '</td>';
1410 echo '</tr>'.NL;
1412 echo '</table>'.NL;
1413 echo '</div>'.NL;
1415 if ($is_img && !$fromajax) echo '</div>';
1419 * Prints two images side by side
1420 * and slider
1422 * @author Kate Arzamastseva <pshns@ukr.net>
1424 * @param string $image image id
1425 * @param int $l_rev revision timestamp, or empty string
1426 * @param int $r_rev revision timestamp, or empty string
1427 * @param array $l_size array with width and height
1428 * @param array $r_size array with width and height
1429 * @param string $type
1431 function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) {
1432 if ($l_size != $r_size) {
1433 if ($r_size[0] > $l_size[0]) {
1434 $l_size = $r_size;
1438 $l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1439 $r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
1441 $l_src = ml($image, $l_more);
1442 $r_src = ml($image, $r_more);
1444 // slider
1445 echo '<div class="slider" style="max-width: '.($l_size[0]-20).'px;" ></div>'.NL;
1447 // two images in divs
1448 echo '<div class="imageDiff ' . $type . '">'.NL;
1449 echo '<div class="image1" style="max-width: '.$l_size[0].'px;">';
1450 echo '<img src="'.$l_src.'" alt="" />';
1451 echo '</div>'.NL;
1452 echo '<div class="image2" style="max-width: '.$l_size[0].'px;">';
1453 echo '<img src="'.$r_src.'" alt="" />';
1454 echo '</div>'.NL;
1455 echo '</div>'.NL;
1459 * Restores an old revision of a media file
1461 * @param string $image media id
1462 * @param int $rev revision timestamp or empty string
1463 * @param int $auth
1464 * @return string - file's id
1466 * @author Kate Arzamastseva <pshns@ukr.net>
1468 function media_restore($image, $rev, $auth){
1469 global $conf;
1470 if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
1471 $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
1472 if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
1473 if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
1474 list(,$imime,) = mimetype($image);
1475 $res = media_upload_finish(mediaFN($image, $rev),
1476 mediaFN($image),
1477 $image,
1478 $imime,
1479 true,
1480 'copy');
1481 if (is_array($res)) {
1482 msg($res[0], $res[1]);
1483 return false;
1485 return $res;
1489 * List all files found by the search request
1491 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1492 * @author Andreas Gohr <gohr@cosmocode.de>
1493 * @author Kate Arzamastseva <pshns@ukr.net>
1494 * @triggers MEDIA_SEARCH
1496 * @param string $query
1497 * @param string $ns
1498 * @param null|int $auth
1499 * @param bool $fullscreen
1500 * @param string $sort
1502 function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort='natural'){
1503 global $conf;
1504 global $lang;
1506 $ns = cleanID($ns);
1507 $evdata = array(
1508 'ns' => $ns,
1509 'data' => array(),
1510 'query' => $query
1512 if (!blank($query)) {
1513 $evt = new Event('MEDIA_SEARCH', $evdata);
1514 if ($evt->advise_before()) {
1515 $dir = utf8_encodeFN(str_replace(':','/',$evdata['ns']));
1516 $quoted = preg_quote($evdata['query'],'/');
1517 //apply globbing
1518 $quoted = str_replace(array('\*', '\?'), array('.*', '.'), $quoted, $count);
1520 //if we use globbing file name must match entirely but may be preceded by arbitrary namespace
1521 if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$';
1523 $pattern = '/'.$quoted.'/i';
1524 search($evdata['data'],
1525 $conf['mediadir'],
1526 'search_media',
1527 array('showmsg'=>false,'pattern'=>$pattern),
1528 $dir,
1530 $sort);
1532 $evt->advise_after();
1533 unset($evt);
1536 if (!$fullscreen) {
1537 echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL;
1538 media_searchform($ns,$query);
1541 if(!count($evdata['data'])){
1542 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
1543 }else {
1544 if ($fullscreen) {
1545 echo '<ul class="' . _media_get_list_type() . '">';
1547 foreach($evdata['data'] as $item){
1548 if (!$fullscreen) media_printfile($item,$item['perm'],'',true);
1549 else media_printfile_thumbs($item,$item['perm'],false,true);
1551 if ($fullscreen) echo '</ul>'.NL;
1556 * Formats and prints one file in the list
1558 * @param array $item
1559 * @param int $auth permission level
1560 * @param string $jump item id
1561 * @param bool $display_namespace
1563 function media_printfile($item,$auth,$jump,$display_namespace=false){
1564 global $lang;
1566 // Prepare zebra coloring
1567 // I always wanted to use this variable name :-D
1568 static $twibble = 1;
1569 $twibble *= -1;
1570 $zebra = ($twibble == -1) ? 'odd' : 'even';
1572 // Automatically jump to recent action
1573 if($jump == $item['id']) {
1574 $jump = ' id="scroll__here" ';
1575 }else{
1576 $jump = '';
1579 // Prepare fileicons
1580 list($ext) = mimetype($item['file'],false);
1581 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
1582 $class = 'select mediafile mf_'.$class;
1584 // Prepare filename
1585 $file = utf8_decodeFN($item['file']);
1587 // Prepare info
1588 $info = '';
1589 if($item['isimg']){
1590 $info .= (int) $item['meta']->getField('File.Width');
1591 $info .= '&#215;';
1592 $info .= (int) $item['meta']->getField('File.Height');
1593 $info .= ' ';
1595 $info .= '<i>'.dformat($item['mtime']).'</i>';
1596 $info .= ' ';
1597 $info .= filesize_h($item['size']);
1599 // output
1600 echo '<div class="'.$zebra.'"'.$jump.' title="'.hsc($item['id']).'">'.NL;
1601 if (!$display_namespace) {
1602 echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($file).'</a> ';
1603 } else {
1604 echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($item['id']).'</a><br/>';
1606 echo '<span class="info">('.$info.')</span>'.NL;
1608 // view button
1609 $link = ml($item['id'],'',true);
1610 echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '.
1611 'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>';
1613 // mediamanager button
1614 $link = wl('',array('do'=>'media','image'=>$item['id'],'ns'=>getNS($item['id'])));
1615 echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/mediamanager.png" '.
1616 'alt="'.$lang['btn_media'].'" title="'.$lang['btn_media'].'" class="btn" /></a>';
1618 // delete button
1619 if($item['writable'] && $auth >= AUTH_DELETE){
1620 $link = DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($item['id']).
1621 '&amp;sectok='.getSecurityToken();
1622 echo ' <a href="'.$link.'" class="btn_media_delete" title="'.$item['id'].'">'.
1623 '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '.
1624 'title="'.$lang['btn_delete'].'" class="btn" /></a>';
1627 echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">';
1628 echo $lang['mediausage'].' <code>{{:'.$item['id'].'}}</code>';
1629 echo '</div>';
1630 if($item['isimg']) media_printimgdetail($item);
1631 echo '<div class="clearer"></div>'.NL;
1632 echo '</div>'.NL;
1636 * Display a media icon
1638 * @param string $filename media id
1639 * @param string $size the size subfolder, if not specified 16x16 is used
1640 * @return string html
1642 function media_printicon($filename, $size=''){
1643 list($ext) = mimetype(mediaFN($filename),false);
1645 if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) {
1646 $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png';
1647 } else {
1648 $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png';
1651 return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
1655 * Formats and prints one file in the list in the thumbnails view
1657 * @author Kate Arzamastseva <pshns@ukr.net>
1659 * @param array $item
1660 * @param int $auth permission level
1661 * @param bool|string $jump item id
1662 * @param bool $display_namespace
1664 function media_printfile_thumbs($item,$auth,$jump=false,$display_namespace=false){
1666 // Prepare filename
1667 $file = utf8_decodeFN($item['file']);
1669 // output
1670 echo '<li><dl title="'.hsc($item['id']).'">'.NL;
1672 echo '<dt>';
1673 if($item['isimg']) {
1674 media_printimgdetail($item, true);
1676 } else {
1677 echo '<a id="d_:'.$item['id'].'" class="image" title="'.$item['id'].'" href="'.
1678 media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']),
1679 'tab_details' => 'view')).'">';
1680 echo media_printicon($item['id'], '32x32');
1681 echo '</a>';
1683 echo '</dt>'.NL;
1684 if (!$display_namespace) {
1685 $name = hsc($file);
1686 } else {
1687 $name = hsc($item['id']);
1689 echo '<dd class="name"><a href="'.media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']),
1690 'tab_details' => 'view')).'" id="h_:'.$item['id'].'">'.$name.'</a></dd>'.NL;
1692 if($item['isimg']){
1693 $size = '';
1694 $size .= (int) $item['meta']->getField('File.Width');
1695 $size .= '&#215;';
1696 $size .= (int) $item['meta']->getField('File.Height');
1697 echo '<dd class="size">'.$size.'</dd>'.NL;
1698 } else {
1699 echo '<dd class="size">&#160;</dd>'.NL;
1701 $date = dformat($item['mtime']);
1702 echo '<dd class="date">'.$date.'</dd>'.NL;
1703 $filesize = filesize_h($item['size']);
1704 echo '<dd class="filesize">'.$filesize.'</dd>'.NL;
1705 echo '</dl></li>'.NL;
1709 * Prints a thumbnail and metainfo
1711 * @param array $item
1712 * @param bool $fullscreen
1714 function media_printimgdetail($item, $fullscreen=false){
1715 // prepare thumbnail
1716 $size = $fullscreen ? 90 : 120;
1718 $w = (int) $item['meta']->getField('File.Width');
1719 $h = (int) $item['meta']->getField('File.Height');
1720 if($w>$size || $h>$size){
1721 if (!$fullscreen) {
1722 $ratio = $item['meta']->getResizeRatio($size);
1723 } else {
1724 $ratio = $item['meta']->getResizeRatio($size,$size);
1726 $w = floor($w * $ratio);
1727 $h = floor($h * $ratio);
1729 $src = ml($item['id'],array('w'=>$w,'h'=>$h,'t'=>$item['mtime']));
1730 $p = array();
1731 if (!$fullscreen) {
1732 // In fullscreen mediamanager view, image resizing is done via CSS.
1733 $p['width'] = $w;
1734 $p['height'] = $h;
1736 $p['alt'] = $item['id'];
1737 $att = buildAttributes($p);
1739 // output
1740 if ($fullscreen) {
1741 echo '<a id="l_:'.$item['id'].'" class="image thumb" href="'.
1742 media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view']).'">';
1743 echo '<img src="'.$src.'" '.$att.' />';
1744 echo '</a>';
1747 if ($fullscreen) return;
1749 echo '<div class="detail">';
1750 echo '<div class="thumb">';
1751 echo '<a id="d_:'.$item['id'].'" class="select">';
1752 echo '<img src="'.$src.'" '.$att.' />';
1753 echo '</a>';
1754 echo '</div>';
1756 // read EXIF/IPTC data
1757 $t = $item['meta']->getField(array('IPTC.Headline','xmp.dc:title'));
1758 $d = $item['meta']->getField(array('IPTC.Caption','EXIF.UserComment',
1759 'EXIF.TIFFImageDescription',
1760 'EXIF.TIFFUserComment'));
1761 if(\dokuwiki\Utf8\PhpString::strlen($d) > 250) $d = \dokuwiki\Utf8\PhpString::substr($d,0,250).'...';
1762 $k = $item['meta']->getField(array('IPTC.Keywords','IPTC.Category','xmp.dc:subject'));
1764 // print EXIF/IPTC data
1765 if($t || $d || $k ){
1766 echo '<p>';
1767 if($t) echo '<strong>'.hsc($t).'</strong><br />';
1768 if($d) echo hsc($d).'<br />';
1769 if($t) echo '<em>'.hsc($k).'</em>';
1770 echo '</p>';
1772 echo '</div>';
1776 * Build link based on the current, adding/rewriting parameters
1778 * @author Kate Arzamastseva <pshns@ukr.net>
1780 * @param array|bool $params
1781 * @param string $amp separator
1782 * @param bool $abs absolute url?
1783 * @param bool $params_array return the parmeters array?
1784 * @return string|array - link or link parameters
1786 function media_managerURL($params=false, $amp='&amp;', $abs=false, $params_array=false) {
1787 global $ID;
1788 global $INPUT;
1790 $gets = array('do' => 'media');
1791 $media_manager_params = array('tab_files', 'tab_details', 'image', 'ns', 'list', 'sort');
1792 foreach ($media_manager_params as $x) {
1793 if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x);
1796 if ($params) {
1797 $gets = $params + $gets;
1799 unset($gets['id']);
1800 if (isset($gets['delete'])) {
1801 unset($gets['image']);
1802 unset($gets['tab_details']);
1805 if ($params_array) return $gets;
1807 return wl($ID,$gets,$abs,$amp);
1811 * Print the media upload form if permissions are correct
1813 * @author Andreas Gohr <andi@splitbrain.org>
1814 * @author Kate Arzamastseva <pshns@ukr.net>
1816 * @param string $ns
1817 * @param int $auth permission level
1818 * @param bool $fullscreen
1820 function media_uploadform($ns, $auth, $fullscreen = false){
1821 global $lang;
1822 global $conf;
1823 global $INPUT;
1825 if($auth < AUTH_UPLOAD) {
1826 echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
1827 return;
1829 $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
1831 $update = false;
1832 $id = '';
1833 if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') {
1834 $update = true;
1835 $id = cleanID($INPUT->str('image'));
1838 // The default HTML upload form
1839 $params = array('id' => 'dw__upload',
1840 'enctype' => 'multipart/form-data');
1841 if (!$fullscreen) {
1842 $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php';
1843 } else {
1844 $params['action'] = media_managerURL(array('tab_files' => 'files',
1845 'tab_details' => 'view'), '&');
1848 $form = new Doku_Form($params);
1849 if (!$fullscreen) echo '<div class="upload">' . $lang['mediaupload'] . '</div>';
1850 $form->addElement(formSecurityToken());
1851 $form->addHidden('ns', hsc($ns));
1852 $form->addElement(form_makeOpenTag('p'));
1853 $form->addElement(form_makeFileField('upload', $lang['txt_upload'], 'upload__file'));
1854 $form->addElement(form_makeCloseTag('p'));
1855 $form->addElement(form_makeOpenTag('p'));
1856 $form->addElement(form_makeTextField('mediaid', noNS($id), $lang['txt_filename'], 'upload__name'));
1857 $form->addElement(form_makeButton('submit', '', $lang['btn_upload']));
1858 $form->addElement(form_makeCloseTag('p'));
1860 if($auth >= $auth_ow){
1861 $form->addElement(form_makeOpenTag('p'));
1862 $attrs = array();
1863 if ($update) $attrs['checked'] = 'checked';
1864 $form->addElement(form_makeCheckboxField('ow', 1, $lang['txt_overwrt'], 'dw__ow', 'check', $attrs));
1865 $form->addElement(form_makeCloseTag('p'));
1868 echo NL.'<div id="mediamanager__uploader">'.NL;
1869 html_form('upload', $form);
1871 echo '</div>'.NL;
1873 echo '<p class="maxsize">';
1874 printf($lang['maxuploadsize'],filesize_h(media_getuploadsize()));
1875 echo ' <a class="allowedmime" href="#">' . $lang['allowedmime'] . '</a>';
1876 echo ' <span>' . implode(', ', array_keys(getMimeTypes())) .'</span>';
1877 echo '</p>'.NL;
1881 * Returns the size uploaded files may have
1883 * This uses a conservative approach using the lowest number found
1884 * in any of the limiting ini settings
1886 * @returns int size in bytes
1888 function media_getuploadsize(){
1889 $okay = 0;
1891 $post = (int) php_to_byte(@ini_get('post_max_size'));
1892 $suho = (int) php_to_byte(@ini_get('suhosin.post.max_value_length'));
1893 $upld = (int) php_to_byte(@ini_get('upload_max_filesize'));
1895 if($post && ($post < $okay || $okay == 0)) $okay = $post;
1896 if($suho && ($suho < $okay || $okay == 0)) $okay = $suho;
1897 if($upld && ($upld < $okay || $okay == 0)) $okay = $upld;
1899 return $okay;
1903 * Print the search field form
1905 * @author Tobias Sarnowski <sarnowski@cosmocode.de>
1906 * @author Kate Arzamastseva <pshns@ukr.net>
1908 * @param string $ns
1909 * @param string $query
1910 * @param bool $fullscreen
1912 function media_searchform($ns,$query='',$fullscreen=false){
1913 global $lang;
1915 // The default HTML search form
1916 $params = array('id' => 'dw__mediasearch');
1917 if (!$fullscreen) {
1918 $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php';
1919 } else {
1920 $params['action'] = media_managerURL(array(), '&');
1922 $form = new Doku_Form($params);
1923 $form->addHidden('ns', $ns);
1924 $form->addHidden($fullscreen ? 'mediado' : 'do', 'searchlist');
1926 $form->addElement(form_makeOpenTag('p'));
1927 $form->addElement(
1928 form_makeTextField(
1929 'q',
1930 $query,
1931 $lang['searchmedia'],
1934 array('title' => sprintf($lang['searchmedia_in'], hsc($ns) . ':*'))
1937 $form->addElement(form_makeButton('submit', '', $lang['btn_search']));
1938 $form->addElement(form_makeCloseTag('p'));
1939 html_form('searchmedia', $form);
1943 * Build a tree outline of available media namespaces
1945 * @author Andreas Gohr <andi@splitbrain.org>
1947 * @param string $ns
1949 function media_nstree($ns){
1950 global $conf;
1951 global $lang;
1953 // currently selected namespace
1954 $ns = cleanID($ns);
1955 if(empty($ns)){
1956 global $ID;
1957 $ns = (string)getNS($ID);
1960 $ns_dir = utf8_encodeFN(str_replace(':','/',$ns));
1962 $data = array();
1963 search($data,$conf['mediadir'],'search_index',array('ns' => $ns_dir, 'nofiles' => true));
1965 // wrap a list with the root level around the other namespaces
1966 array_unshift($data, array('level' => 0, 'id' => '', 'open' =>'true',
1967 'label' => '['.$lang['mediaroot'].']'));
1969 // insert the current ns into the hierarchy if it isn't already part of it
1970 $ns_parts = explode(':', $ns);
1971 $tmp_ns = '';
1972 $pos = 0;
1973 foreach ($ns_parts as $level => $part) {
1974 if ($tmp_ns) $tmp_ns .= ':'.$part;
1975 else $tmp_ns = $part;
1977 // find the namespace parts or insert them
1978 while ($data[$pos]['id'] != $tmp_ns) {
1979 if (
1980 $pos >= count($data) ||
1981 ($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
1983 array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true')));
1984 break;
1986 ++$pos;
1990 echo html_buildlist($data,'idx','media_nstree_item','media_nstree_li');
1994 * Userfunction for html_buildlist
1996 * Prints a media namespace tree item
1998 * @author Andreas Gohr <andi@splitbrain.org>
2000 * @param array $item
2001 * @return string html
2003 function media_nstree_item($item){
2004 global $INPUT;
2005 $pos = strrpos($item['id'], ':');
2006 $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0);
2007 if(empty($item['label'])) $item['label'] = $label;
2009 $ret = '';
2010 if (!($INPUT->str('do') == 'media'))
2011 $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
2012 else $ret .= '<a href="'.media_managerURL(array('ns' => idfilter($item['id'], false), 'tab_files' => 'files'))
2013 .'" class="idx_dir">';
2014 $ret .= $item['label'];
2015 $ret .= '</a>';
2016 return $ret;
2020 * Userfunction for html_buildlist
2022 * Prints a media namespace tree item opener
2024 * @author Andreas Gohr <andi@splitbrain.org>
2026 * @param array $item
2027 * @return string html
2029 function media_nstree_li($item){
2030 $class='media level'.$item['level'];
2031 if($item['open']){
2032 $class .= ' open';
2033 $img = DOKU_BASE.'lib/images/minus.gif';
2034 $alt = '−';
2035 }else{
2036 $class .= ' closed';
2037 $img = DOKU_BASE.'lib/images/plus.gif';
2038 $alt = '+';
2040 // TODO: only deliver an image if it actually has a subtree...
2041 return '<li class="'.$class.'">'.
2042 '<img src="'.$img.'" alt="'.$alt.'" />';
2046 * Resizes the given image to the given size
2048 * @author Andreas Gohr <andi@splitbrain.org>
2050 * @param string $file filename, path to file
2051 * @param string $ext extension
2052 * @param int $w desired width
2053 * @param int $h desired height
2054 * @return string path to resized or original size if failed
2056 function media_resize_image($file, $ext, $w, $h=0){
2057 global $conf;
2059 $info = @getimagesize($file); //get original size
2060 if($info == false) return $file; // that's no image - it's a spaceship!
2062 if(!$h) $h = round(($w * $info[1]) / $info[0]);
2063 if(!$w) $w = round(($h * $info[0]) / $info[1]);
2065 // we wont scale up to infinity
2066 if($w > 2000 || $h > 2000) return $file;
2068 // resize necessary? - (w,h) = native dimensions
2069 if(($w == $info[0]) && ($h == $info[1])) return $file;
2071 //cache
2072 $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
2073 $mtime = @filemtime($local); // 0 if not exists
2075 if($mtime > filemtime($file) ||
2076 media_resize_imageIM($ext, $file, $info[0], $info[1], $local, $w, $h) ||
2077 media_resize_imageGD($ext, $file, $info[0], $info[1], $local, $w, $h)
2079 if($conf['fperm']) @chmod($local, $conf['fperm']);
2080 return $local;
2082 //still here? resizing failed
2083 return $file;
2087 * Crops the given image to the wanted ratio, then calls media_resize_image to scale it
2088 * to the wanted size
2090 * Crops are centered horizontally but prefer the upper third of an vertical
2091 * image because most pics are more interesting in that area (rule of thirds)
2093 * @author Andreas Gohr <andi@splitbrain.org>
2095 * @param string $file filename, path to file
2096 * @param string $ext extension
2097 * @param int $w desired width
2098 * @param int $h desired height
2099 * @return string path to resized or original size if failed
2101 function media_crop_image($file, $ext, $w, $h=0){
2102 global $conf;
2104 if(!$h) $h = $w;
2105 $info = @getimagesize($file); //get original size
2106 if($info == false) return $file; // that's no image - it's a spaceship!
2108 // calculate crop size
2109 $fr = $info[0]/$info[1];
2110 $tr = $w/$h;
2112 // check if the crop can be handled completely by resize,
2113 // i.e. the specified width & height match the aspect ratio of the source image
2114 if ($w == round($h*$fr)) {
2115 return media_resize_image($file, $ext, $w);
2118 if($tr >= 1){
2119 if($tr > $fr){
2120 $cw = $info[0];
2121 $ch = (int) ($info[0]/$tr);
2122 }else{
2123 $cw = (int) ($info[1]*$tr);
2124 $ch = $info[1];
2126 }else{
2127 if($tr < $fr){
2128 $cw = (int) ($info[1]*$tr);
2129 $ch = $info[1];
2130 }else{
2131 $cw = $info[0];
2132 $ch = (int) ($info[0]/$tr);
2135 // calculate crop offset
2136 $cx = (int) (($info[0]-$cw)/2);
2137 $cy = (int) (($info[1]-$ch)/3);
2139 //cache
2140 $local = getCacheName($file,'.media.'.$cw.'x'.$ch.'.crop.'.$ext);
2141 $mtime = @filemtime($local); // 0 if not exists
2143 if( $mtime > @filemtime($file) ||
2144 media_crop_imageIM($ext,$file,$info[0],$info[1],$local,$cw,$ch,$cx,$cy) ||
2145 media_resize_imageGD($ext,$file,$cw,$ch,$local,$cw,$ch,$cx,$cy) ){
2146 if($conf['fperm']) @chmod($local, $conf['fperm']);
2147 return media_resize_image($local,$ext, $w, $h);
2150 //still here? cropping failed
2151 return media_resize_image($file,$ext, $w, $h);
2155 * Calculate a token to be used to verify fetch requests for resized or
2156 * cropped images have been internally generated - and prevent external
2157 * DDOS attacks via fetch
2159 * @author Christopher Smith <chris@jalakai.co.uk>
2161 * @param string $id id of the image
2162 * @param int $w resize/crop width
2163 * @param int $h resize/crop height
2164 * @return string token or empty string if no token required
2166 function media_get_token($id,$w,$h){
2167 // token is only required for modified images
2168 if ($w || $h || media_isexternal($id)) {
2169 $token = $id;
2170 if ($w) $token .= '.'.$w;
2171 if ($h) $token .= '.'.$h;
2173 return substr(\dokuwiki\PassHash::hmac('md5', $token, auth_cookiesalt()),0,6);
2176 return '';
2180 * Download a remote file and return local filename
2182 * returns false if download fails. Uses cached file if available and
2183 * wanted
2185 * @author Andreas Gohr <andi@splitbrain.org>
2186 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
2188 * @param string $url
2189 * @param string $ext extension
2190 * @param int $cache cachetime in seconds
2191 * @return false|string path to cached file
2193 function media_get_from_URL($url,$ext,$cache){
2194 global $conf;
2196 // if no cache or fetchsize just redirect
2197 if ($cache==0) return false;
2198 if (!$conf['fetchsize']) return false;
2200 $local = getCacheName(strtolower($url),".media.$ext");
2201 $mtime = @filemtime($local); // 0 if not exists
2203 //decide if download needed:
2204 if(($mtime == 0) || // cache does not exist
2205 ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired
2207 if(media_image_download($url, $local)) {
2208 return $local;
2209 } else {
2210 return false;
2214 //if cache exists use it else
2215 if($mtime) return $local;
2217 //else return false
2218 return false;
2222 * Download image files
2224 * @author Andreas Gohr <andi@splitbrain.org>
2226 * @param string $url
2227 * @param string $file path to file in which to put the downloaded content
2228 * @return bool
2230 function media_image_download($url,$file){
2231 global $conf;
2232 $http = new DokuHTTPClient();
2233 $http->keep_alive = false; // we do single ops here, no need for keep-alive
2235 $http->max_bodysize = $conf['fetchsize'];
2236 $http->timeout = 25; //max. 25 sec
2237 $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
2239 $data = $http->get($url);
2240 if(!$data) return false;
2242 $fileexists = file_exists($file);
2243 $fp = @fopen($file,"w");
2244 if(!$fp) return false;
2245 fwrite($fp,$data);
2246 fclose($fp);
2247 if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
2249 // check if it is really an image
2250 $info = @getimagesize($file);
2251 if(!$info){
2252 @unlink($file);
2253 return false;
2256 return true;
2260 * resize images using external ImageMagick convert program
2262 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
2263 * @author Andreas Gohr <andi@splitbrain.org>
2265 * @param string $ext extension
2266 * @param string $from filename path to file
2267 * @param int $from_w original width
2268 * @param int $from_h original height
2269 * @param string $to path to resized file
2270 * @param int $to_w desired width
2271 * @param int $to_h desired height
2272 * @return bool
2274 function media_resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
2275 global $conf;
2277 // check if convert is configured
2278 if(!$conf['im_convert']) return false;
2280 // prepare command
2281 $cmd = $conf['im_convert'];
2282 $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
2283 if ($ext == 'jpg' || $ext == 'jpeg') {
2284 $cmd .= ' -quality '.$conf['jpg_quality'];
2286 $cmd .= " $from $to";
2288 @exec($cmd,$out,$retval);
2289 if ($retval == 0) return true;
2290 return false;
2294 * crop images using external ImageMagick convert program
2296 * @author Andreas Gohr <andi@splitbrain.org>
2298 * @param string $ext extension
2299 * @param string $from filename path to file
2300 * @param int $from_w original width
2301 * @param int $from_h original height
2302 * @param string $to path to resized file
2303 * @param int $to_w desired width
2304 * @param int $to_h desired height
2305 * @param int $ofs_x offset of crop centre
2306 * @param int $ofs_y offset of crop centre
2307 * @return bool
2309 function media_crop_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x,$ofs_y){
2310 global $conf;
2312 // check if convert is configured
2313 if(!$conf['im_convert']) return false;
2315 // prepare command
2316 $cmd = $conf['im_convert'];
2317 $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y;
2318 if ($ext == 'jpg' || $ext == 'jpeg') {
2319 $cmd .= ' -quality '.$conf['jpg_quality'];
2321 $cmd .= " $from $to";
2323 @exec($cmd,$out,$retval);
2324 if ($retval == 0) return true;
2325 return false;
2329 * resize or crop images using PHP's libGD support
2331 * @author Andreas Gohr <andi@splitbrain.org>
2332 * @author Sebastian Wienecke <s_wienecke@web.de>
2334 * @param string $ext extension
2335 * @param string $from filename path to file
2336 * @param int $from_w original width
2337 * @param int $from_h original height
2338 * @param string $to path to resized file
2339 * @param int $to_w desired width
2340 * @param int $to_h desired height
2341 * @param int $ofs_x offset of crop centre
2342 * @param int $ofs_y offset of crop centre
2343 * @return bool
2345 function media_resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x=0,$ofs_y=0){
2346 global $conf;
2348 if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
2350 // check available memory
2351 if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
2352 return false;
2355 // create an image of the given filetype
2356 $image = false;
2357 if ($ext == 'jpg' || $ext == 'jpeg'){
2358 if(!function_exists("imagecreatefromjpeg")) return false;
2359 $image = @imagecreatefromjpeg($from);
2360 }elseif($ext == 'png') {
2361 if(!function_exists("imagecreatefrompng")) return false;
2362 $image = @imagecreatefrompng($from);
2364 }elseif($ext == 'gif') {
2365 if(!function_exists("imagecreatefromgif")) return false;
2366 $image = @imagecreatefromgif($from);
2368 if(!$image) return false;
2370 $newimg = false;
2371 if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){
2372 $newimg = @imagecreatetruecolor ($to_w, $to_h);
2374 if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
2375 if(!$newimg){
2376 imagedestroy($image);
2377 return false;
2380 //keep png alpha channel if possible
2381 if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
2382 imagealphablending($newimg, false);
2383 imagesavealpha($newimg,true);
2386 //keep gif transparent color if possible
2387 if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) {
2388 if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) {
2389 $transcolorindex = @imagecolortransparent($image);
2390 if($transcolorindex >= 0 ) { //transparent color exists
2391 $transcolor = @imagecolorsforindex($image, $transcolorindex);
2392 $transcolorindex = @imagecolorallocate(
2393 $newimg,
2394 $transcolor['red'],
2395 $transcolor['green'],
2396 $transcolor['blue']
2398 @imagefill($newimg, 0, 0, $transcolorindex);
2399 @imagecolortransparent($newimg, $transcolorindex);
2400 }else{ //filling with white
2401 $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2402 @imagefill($newimg, 0, 0, $whitecolorindex);
2404 }else{ //filling with white
2405 $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255);
2406 @imagefill($newimg, 0, 0, $whitecolorindex);
2410 //try resampling first
2411 if(function_exists("imagecopyresampled")){
2412 if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) {
2413 imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2415 }else{
2416 imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h);
2419 $okay = false;
2420 if ($ext == 'jpg' || $ext == 'jpeg'){
2421 if(!function_exists('imagejpeg')){
2422 $okay = false;
2423 }else{
2424 $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
2426 }elseif($ext == 'png') {
2427 if(!function_exists('imagepng')){
2428 $okay = false;
2429 }else{
2430 $okay = imagepng($newimg, $to);
2432 }elseif($ext == 'gif') {
2433 if(!function_exists('imagegif')){
2434 $okay = false;
2435 }else{
2436 $okay = imagegif($newimg, $to);
2440 // destroy GD image ressources
2441 if($image) imagedestroy($image);
2442 if($newimg) imagedestroy($newimg);
2444 return $okay;
2448 * Return other media files with the same base name
2449 * but different extensions.
2451 * @param string $src - ID of media file
2452 * @param string[] $exts - alternative extensions to find other files for
2453 * @return array - array(mime type => file ID)
2455 * @author Anika Henke <anika@selfthinker.org>
2457 function media_alternativefiles($src, $exts){
2459 $files = array();
2460 list($srcExt, /* $srcMime */) = mimetype($src);
2461 $filebase = substr($src, 0, -1 * (strlen($srcExt)+1));
2463 foreach($exts as $ext) {
2464 $fileid = $filebase.'.'.$ext;
2465 $file = mediaFN($fileid);
2466 if(file_exists($file)) {
2467 list(/* $fileExt */, $fileMime) = mimetype($file);
2468 $files[$fileMime] = $fileid;
2471 return $files;
2475 * Check if video/audio is supported to be embedded.
2477 * @param string $mime - mimetype of media file
2478 * @param string $type - type of media files to check ('video', 'audio', or null for all)
2479 * @return boolean
2481 * @author Anika Henke <anika@selfthinker.org>
2483 function media_supportedav($mime, $type=NULL){
2484 $supportedAudio = array(
2485 'ogg' => 'audio/ogg',
2486 'mp3' => 'audio/mpeg',
2487 'wav' => 'audio/wav',
2489 $supportedVideo = array(
2490 'webm' => 'video/webm',
2491 'ogv' => 'video/ogg',
2492 'mp4' => 'video/mp4',
2494 if ($type == 'audio') {
2495 $supportedAv = $supportedAudio;
2496 } elseif ($type == 'video') {
2497 $supportedAv = $supportedVideo;
2498 } else {
2499 $supportedAv = array_merge($supportedAudio, $supportedVideo);
2501 return in_array($mime, $supportedAv);
2505 * Return track media files with the same base name
2506 * but extensions that indicate kind and lang.
2507 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt...
2509 * @param string $src - ID of media file
2510 * @return array - array(mediaID => array( kind, srclang ))
2512 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
2514 function media_trackfiles($src){
2515 $kinds=array(
2516 'sub' => 'subtitles',
2517 'cap' => 'captions',
2518 'des' => 'descriptions',
2519 'cha' => 'chapters',
2520 'met' => 'metadata'
2523 $files = array();
2524 $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/';
2525 $baseid=pathinfo($src, PATHINFO_FILENAME);
2526 $pattern=mediaFN($baseid).'.*.*.vtt';
2527 $list=glob($pattern);
2528 foreach($list as $track) {
2529 if(preg_match($re, $track, $matches)){
2530 $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=array(
2531 $kinds[$matches[1]],
2532 $matches[2],
2536 return $files;
2539 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */