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