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