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 $hidden++; 49 } 50 if ($hidden) { 51 print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>'; 52 } 53} 54 55/** 56 * Handles the saving of image meta data 57 * 58 * @author Andreas Gohr <andi@splitbrain.org> 59 * @author Kate Arzamastseva <pshns@ukr.net> 60 * 61 * @param string $id media id 62 * @param int $auth permission level 63 * @param array $data 64 * @return false|string 65 */ 66function media_metasave($id, $auth, $data) 67{ 68 if ($auth < AUTH_UPLOAD) return false; 69 if (!checkSecurityToken()) return false; 70 global $lang; 71 global $conf; 72 $src = mediaFN($id); 73 74 $meta = new JpegMeta($src); 75 $meta->_parseAll(); 76 77 foreach ($data as $key => $val) { 78 $val=trim($val); 79 if (empty($val)) { 80 $meta->deleteField($key); 81 } else { 82 $meta->setField($key, $val); 83 } 84 } 85 86 $old = @filemtime($src); 87 if (!file_exists(mediaFN($id, $old)) && file_exists($src)) { 88 // add old revision to the attic 89 media_saveOldRevision($id); 90 } 91 $filesize_old = filesize($src); 92 if ($meta->save()) { 93 if ($conf['fperm']) chmod($src, $conf['fperm']); 94 @clearstatcache(true, $src); 95 $new = @filemtime($src); 96 $filesize_new = filesize($src); 97 $sizechange = $filesize_new - $filesize_old; 98 99 // add a log entry to the media changelog 100 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange); 101 102 msg($lang['metasaveok'], 1); 103 return $id; 104 } else { 105 msg($lang['metasaveerr'], -1); 106 return false; 107 } 108} 109 110/** 111 * check if a media is external source 112 * 113 * @author Gerrit Uitslag <klapinklapin@gmail.com> 114 * 115 * @param string $id the media ID or URL 116 * @return bool 117 */ 118function media_isexternal($id) 119{ 120 if (preg_match('#^(?:https?|ftp)://#i', $id)) return true; 121 return false; 122} 123 124/** 125 * Check if a media item is public (eg, external URL or readable by @ALL) 126 * 127 * @author Andreas Gohr <andi@splitbrain.org> 128 * 129 * @param string $id the media ID or URL 130 * @return bool 131 */ 132function media_ispublic($id) 133{ 134 if (media_isexternal($id)) return true; 135 $id = cleanID($id); 136 if (auth_aclcheck(getNS($id).':*', '', []) >= AUTH_READ) return true; 137 return false; 138} 139 140/** 141 * Display the form to edit image meta data 142 * 143 * @author Andreas Gohr <andi@splitbrain.org> 144 * @author Kate Arzamastseva <pshns@ukr.net> 145 * 146 * @param string $id media id 147 * @param int $auth permission level 148 * @return bool 149 */ 150function media_metaform($id, $auth) 151{ 152 global $lang; 153 154 if ($auth < AUTH_UPLOAD) { 155 echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.DOKU_LF; 156 return false; 157 } 158 159 // load the field descriptions 160 static $fields = null; 161 if ($fields === null) { 162 $config_files = getConfigFiles('mediameta'); 163 foreach ($config_files as $config_file) { 164 if (file_exists($config_file)) include($config_file); 165 } 166 } 167 168 $src = mediaFN($id); 169 170 // output 171 $form = new Form([ 172 'action' => media_managerURL(['tab_details' => 'view'], '&'), 173 'class' => 'meta' 174 ]); 175 $form->addTagOpen('div')->addClass('no'); 176 $form->setHiddenField('img', $id); 177 $form->setHiddenField('mediado', 'save'); 178 foreach ($fields as $key => $field) { 179 // get current value 180 if (empty($field[0])) continue; 181 $tags = [$field[0]]; 182 if (is_array($field[3])) $tags = array_merge($tags, $field[3]); 183 $value = tpl_img_getTag($tags, '', $src); 184 $value = cleanText($value); 185 186 // prepare attributes 187 $p = [ 188 'class' => 'edit', 189 'id' => 'meta__'.$key, 190 'name' => 'meta['.$field[0].']' 191 ]; 192 193 $form->addTagOpen('div')->addClass('row'); 194 if ($field[2] == 'text') { 195 $form->addTextInput( 196 $p['name'], 197 ($lang[$field[1]] ?: $field[1] . ':') 198 )->id($p['id'])->addClass($p['class'])->val($value); 199 } else { 200 $form->addTextarea($p['name'], $lang[$field[1]])->id($p['id']) 201 ->val(formText($value)) 202 ->addClass($p['class']) 203 ->attr('rows', '6')->attr('cols', '50'); 204 } 205 $form->addTagClose('div'); 206 } 207 $form->addTagOpen('div')->addClass('buttons'); 208 $form->addButton('mediado[save]', $lang['btn_save'])->attr('type', 'submit') 209 ->attrs(['accesskey' => 's']); 210 $form->addTagClose('div'); 211 212 $form->addTagClose('div'); 213 echo $form->toHTML(); 214 return true; 215} 216 217/** 218 * Convenience function to check if a media file is still in use 219 * 220 * @author Michael Klier <chi@chimeric.de> 221 * 222 * @param string $id media id 223 * @return array|bool 224 */ 225function media_inuse($id) 226{ 227 global $conf; 228 229 if ($conf['refcheck']) { 230 $mediareferences = ft_mediause($id, true); 231 if ($mediareferences === []) { 232 return false; 233 } else { 234 return $mediareferences; 235 } 236 } else { 237 return false; 238 } 239} 240 241/** 242 * Handles media file deletions 243 * 244 * If configured, checks for media references before deletion 245 * 246 * @author Andreas Gohr <andi@splitbrain.org> 247 * 248 * @param string $id media id 249 * @param int $auth no longer used 250 * @return int One of: 0, 251 * DOKU_MEDIA_DELETED, 252 * DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS, 253 * DOKU_MEDIA_NOT_AUTH, 254 * DOKU_MEDIA_INUSE 255 */ 256function media_delete($id, $auth) 257{ 258 global $lang; 259 $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':')); 260 if ($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH; 261 if (media_inuse($id)) return DOKU_MEDIA_INUSE; 262 263 $file = mediaFN($id); 264 265 // trigger an event - MEDIA_DELETE_FILE 266 $data = []; 267 $data['id'] = $id; 268 $data['name'] = PhpString::basename($file); 269 $data['path'] = $file; 270 $data['size'] = (file_exists($file)) ? filesize($file) : 0; 271 272 $data['unl'] = false; 273 $data['del'] = false; 274 $evt = new Event('MEDIA_DELETE_FILE', $data); 275 if ($evt->advise_before()) { 276 $old = @filemtime($file); 277 if (!file_exists(mediaFN($id, $old)) && file_exists($file)) { 278 // add old revision to the attic 279 media_saveOldRevision($id); 280 } 281 282 $data['unl'] = @unlink($file); 283 if ($data['unl']) { 284 $sizechange = 0 - $data['size']; 285 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange); 286 287 $data['del'] = io_sweepNS($id, 'mediadir'); 288 } 289 } 290 $evt->advise_after(); 291 unset($evt); 292 293 if ($data['unl'] && $data['del']) { 294 return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS; 295 } 296 297 return $data['unl'] ? DOKU_MEDIA_DELETED : 0; 298} 299 300/** 301 * Handle file uploads via XMLHttpRequest 302 * 303 * @param string $ns target namespace 304 * @param int $auth current auth check result 305 * @return false|string false on error, id of the new file on success 306 */ 307function media_upload_xhr($ns, $auth) 308{ 309 if (!checkSecurityToken()) return false; 310 global $INPUT; 311 312 $id = $INPUT->get->str('qqfile'); 313 [$ext, $mime] = mimetype($id); 314 $input = fopen("php://input", "r"); 315 if (!($tmp = io_mktmpdir())) return false; 316 $path = $tmp.'/'.md5($id); 317 $target = fopen($path, "w"); 318 $realSize = stream_copy_to_stream($input, $target); 319 fclose($target); 320 fclose($input); 321 if ($INPUT->server->has('CONTENT_LENGTH') && ($realSize != $INPUT->server->int('CONTENT_LENGTH'))) { 322 unlink($path); 323 return false; 324 } 325 326 $res = media_save( 327 ['name' => $path, 'mime' => $mime, 'ext' => $ext], 328 $ns.':'.$id, 329 ($INPUT->get->str('ow') == 'true'), 330 $auth, 331 'copy' 332 ); 333 unlink($path); 334 if ($tmp) io_rmdir($tmp, true); 335 if (is_array($res)) { 336 msg($res[0], $res[1]); 337 return false; 338 } 339 return $res; 340} 341 342/** 343 * Handles media file uploads 344 * 345 * @author Andreas Gohr <andi@splitbrain.org> 346 * @author Michael Klier <chi@chimeric.de> 347 * 348 * @param string $ns target namespace 349 * @param int $auth current auth check result 350 * @param bool|array $file $_FILES member, $_FILES['upload'] if false 351 * @return false|string false on error, id of the new file on success 352 */ 353function media_upload($ns, $auth, $file = false) 354{ 355 if (!checkSecurityToken()) return false; 356 global $lang; 357 global $INPUT; 358 359 // get file and id 360 $id = $INPUT->post->str('mediaid'); 361 if (!$file) $file = $_FILES['upload']; 362 if (empty($id)) $id = $file['name']; 363 364 // check for errors (messages are done in lib/exe/mediamanager.php) 365 if ($file['error']) return false; 366 367 // check extensions 368 [$fext, $fmime] = mimetype($file['name']); 369 [$iext, $imime] = mimetype($id); 370 if ($fext && !$iext) { 371 // no extension specified in id - read original one 372 $id .= '.'.$fext; 373 $imime = $fmime; 374 } elseif ($fext && $fext != $iext) { 375 // extension was changed, print warning 376 msg(sprintf($lang['mediaextchange'], $fext, $iext)); 377 } 378 379 $res = media_save( 380 [ 381 'name' => $file['tmp_name'], 382 'mime' => $imime, 383 'ext' => $iext 384 ], 385 $ns . ':' . $id, 386 $INPUT->post->bool('ow'), 387 $auth, 388 'copy_uploaded_file' 389 ); 390 if (is_array($res)) { 391 msg($res[0], $res[1]); 392 return false; 393 } 394 return $res; 395} 396 397/** 398 * An alternative to move_uploaded_file that copies 399 * 400 * Using copy, makes sure any setgid bits on the media directory are honored 401 * 402 * @see move_uploaded_file() 403 * 404 * @param string $from 405 * @param string $to 406 * @return bool 407 */ 408function copy_uploaded_file($from, $to) 409{ 410 if (!is_uploaded_file($from)) return false; 411 $ok = copy($from, $to); 412 @unlink($from); 413 return $ok; 414} 415 416/** 417 * This generates an action event and delegates to _media_upload_action(). 418 * Action plugins are allowed to pre/postprocess the uploaded file. 419 * (The triggered event is preventable.) 420 * 421 * Event data: 422 * $data[0] fn_tmp: the temporary file name (read from $_FILES) 423 * $data[1] fn: the file name of the uploaded file 424 * $data[2] id: the future directory id of the uploaded file 425 * $data[3] imime: the mimetype of the uploaded file 426 * $data[4] overwrite: if an existing file is going to be overwritten 427 * $data[5] move: name of function that performs move/copy/.. 428 * 429 * @triggers MEDIA_UPLOAD_FINISH 430 * 431 * @param array $file 432 * @param string $id media id 433 * @param bool $ow overwrite? 434 * @param int $auth permission level 435 * @param string $move name of functions that performs move/copy/.. 436 * @return false|array|string 437 */ 438function media_save($file, $id, $ow, $auth, $move) 439{ 440 if ($auth < AUTH_UPLOAD) { 441 return ["You don't have permissions to upload files.", -1]; 442 } 443 444 if (!isset($file['mime']) || !isset($file['ext'])) { 445 [$ext, $mime] = mimetype($id); 446 if (!isset($file['mime'])) { 447 $file['mime'] = $mime; 448 } 449 if (!isset($file['ext'])) { 450 $file['ext'] = $ext; 451 } 452 } 453 454 global $lang, $conf; 455 456 // get filename 457 $id = cleanID($id); 458 $fn = mediaFN($id); 459 460 // get filetype regexp 461 $types = array_keys(getMimeTypes()); 462 $types = array_map( 463 static fn($q) => preg_quote($q, "/"), 464 $types 465 ); 466 $regex = implode('|', $types); 467 468 // because a temp file was created already 469 if (!preg_match('/\.('.$regex.')$/i', $fn)) { 470 return [$lang['uploadwrong'], -1]; 471 } 472 473 //check for overwrite 474 $overwrite = file_exists($fn); 475 $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE); 476 if ($overwrite && (!$ow || $auth < $auth_ow)) { 477 return [$lang['uploadexist'], 0]; 478 } 479 // check for valid content 480 $ok = media_contentcheck($file['name'], $file['mime']); 481 if ($ok == -1) { 482 return [sprintf($lang['uploadbadcontent'], '.' . $file['ext']), -1]; 483 } elseif ($ok == -2) { 484 return [$lang['uploadspam'], -1]; 485 } elseif ($ok == -3) { 486 return [$lang['uploadxss'], -1]; 487 } 488 489 // prepare event data 490 $data = []; 491 $data[0] = $file['name']; 492 $data[1] = $fn; 493 $data[2] = $id; 494 $data[3] = $file['mime']; 495 $data[4] = $overwrite; 496 $data[5] = $move; 497 498 // trigger event 499 return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true); 500} 501 502/** 503 * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH 504 * 505 * @author Michael Klier <chi@chimeric.de> 506 * 507 * @param array $data event data 508 * @return false|array|string 509 */ 510function _media_upload_action($data) 511{ 512 // fixme do further sanity tests of given data? 513 if (is_array($data) && count($data)===6) { 514 return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]); 515 } else { 516 return false; //callback error 517 } 518} 519 520/** 521 * Saves an uploaded media file 522 * 523 * @author Andreas Gohr <andi@splitbrain.org> 524 * @author Michael Klier <chi@chimeric.de> 525 * @author Kate Arzamastseva <pshns@ukr.net> 526 * 527 * @param string $fn_tmp 528 * @param string $fn 529 * @param string $id media id 530 * @param string $imime mime type 531 * @param bool $overwrite overwrite existing? 532 * @param string $move function name 533 * @return array|string 534 */ 535function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') 536{ 537 global $conf; 538 global $lang; 539 global $REV; 540 541 $old = @filemtime($fn); 542 if (!file_exists(mediaFN($id, $old)) && file_exists($fn)) { 543 // add old revision to the attic if missing 544 media_saveOldRevision($id); 545 } 546 547 // prepare directory 548 io_createNamespace($id, 'media'); 549 550 $filesize_old = file_exists($fn) ? filesize($fn) : 0; 551 552 if ($move($fn_tmp, $fn)) { 553 @clearstatcache(true, $fn); 554 $new = @filemtime($fn); 555 // Set the correct permission here. 556 // Always chmod media because they may be saved with different permissions than expected from the php umask. 557 // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.) 558 chmod($fn, $conf['fmode']); 559 msg($lang['uploadsucc'], 1); 560 media_notify($id, $fn, $imime, $old, $new); 561 // add a log entry to the media changelog 562 $filesize_new = filesize($fn); 563 $sizechange = $filesize_new - $filesize_old; 564 if ($REV) { 565 addMediaLogEntry( 566 $new, 567 $id, 568 DOKU_CHANGE_TYPE_REVERT, 569 sprintf($lang['restored'], dformat($REV)), 570 $REV, 571 null, 572 $sizechange 573 ); 574 } elseif ($overwrite) { 575 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange); 576 } else { 577 addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange); 578 } 579 return $id; 580 } else { 581 return [$lang['uploadfail'], -1]; 582 } 583} 584 585/** 586 * Moves the current version of media file to the media_attic 587 * directory 588 * 589 * @author Kate Arzamastseva <pshns@ukr.net> 590 * 591 * @param string $id 592 * @return int - revision date 593 */ 594function media_saveOldRevision($id) 595{ 596 global $conf, $lang; 597 598 $oldf = mediaFN($id); 599 if (!file_exists($oldf)) return ''; 600 $date = filemtime($oldf); 601 if (!$conf['mediarevisions']) return $date; 602 603 $medialog = new MediaChangeLog($id); 604 if (!$medialog->getRevisionInfo($date)) { 605 // there was an external edit, 606 // there is no log entry for current version of file 607 $sizechange = filesize($oldf); 608 if (!file_exists(mediaMetaFN($id, '.changes'))) { 609 addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange); 610 } else { 611 $oldRev = $medialog->getRevisions(-1, 1); // from changelog 612 $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]); 613 $filesize_old = filesize(mediaFN($id, $oldRev)); 614 $sizechange -= $filesize_old; 615 616 addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange); 617 } 618 } 619 620 $newf = mediaFN($id, $date); 621 io_makeFileDir($newf); 622 if (copy($oldf, $newf)) { 623 // Set the correct permission here. 624 // Always chmod media because they may be saved with different permissions than expected from the php umask. 625 // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.) 626 chmod($newf, $conf['fmode']); 627 } 628 return $date; 629} 630 631/** 632 * This function checks if the uploaded content is really what the 633 * mimetype says it is. We also do spam checking for text types here. 634 * 635 * We need to do this stuff because we can not rely on the browser 636 * to do this check correctly. Yes, IE is broken as usual. 637 * 638 * @author Andreas Gohr <andi@splitbrain.org> 639 * @link http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting 640 * @fixme check all 26 magic IE filetypes here? 641 * 642 * @param string $file path to file 643 * @param string $mime mimetype 644 * @return int 645 */ 646function media_contentcheck($file, $mime) 647{ 648 global $conf; 649 if ($conf['iexssprotect']) { 650 $fh = @fopen($file, 'rb'); 651 if ($fh) { 652 $bytes = fread($fh, 256); 653 fclose($fh); 654 if (preg_match('/<(script|a|img|html|body|iframe)[\s>]/i', $bytes)) { 655 return -3; //XSS: possibly malicious content 656 } 657 } 658 } 659 if (substr($mime, 0, 6) == 'image/') { 660 $info = @getimagesize($file); 661 if ($mime == 'image/gif' && $info[2] != 1) { 662 return -1; // uploaded content did not match the file extension 663 } elseif ($mime == 'image/jpeg' && $info[2] != 2) { 664 return -1; 665 } elseif ($mime == 'image/png' && $info[2] != 3) { 666 return -1; 667 } 668 # fixme maybe check other images types as well 669 } elseif (substr($mime, 0, 5) == 'text/') { 670 global $TEXT; 671 $TEXT = io_readFile($file); 672 if (checkwordblock()) { 673 return -2; //blocked by the spam blacklist 674 } 675 } 676 return 0; 677} 678 679/** 680 * Send a notify mail on uploads 681 * 682 * @author Andreas Gohr <andi@splitbrain.org> 683 * 684 * @param string $id media id 685 * @param string $file path to file 686 * @param string $mime mime type 687 * @param bool|int $old_rev revision timestamp or false 688 */ 689function media_notify($id, $file, $mime, $old_rev = false, $current_rev = false) 690{ 691 global $conf; 692 if (empty($conf['notify'])) return; //notify enabled? 693 694 $subscription = new MediaSubscriptionSender(); 695 $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev); 696} 697 698/** 699 * List all files in a given Media namespace 700 * 701 * @param string $ns namespace 702 * @param null|int $auth permission level 703 * @param string $jump id 704 * @param bool $fullscreenview 705 * @param bool|string $sort sorting order, false skips sorting 706 */ 707function media_filelist($ns, $auth = null, $jump = '', $fullscreenview = false, $sort = false) 708{ 709 global $conf; 710 global $lang; 711 $ns = cleanID($ns); 712 713 // check auth our self if not given (needed for ajax calls) 714 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 715 716 if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL; 717 718 if ($auth < AUTH_READ) { 719 // FIXME: print permission warning here instead? 720 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; 721 } else { 722 if (!$fullscreenview) { 723 media_uploadform($ns, $auth); 724 media_searchform($ns); 725 } 726 727 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 728 $data = []; 729 search( 730 $data, 731 $conf['mediadir'], 732 'search_mediafiles', 733 ['showmsg'=>true, 'depth'=>1], 734 $dir, 735 1, 736 $sort 737 ); 738 739 if (!count($data)) { 740 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; 741 } else { 742 if ($fullscreenview) { 743 echo '<ul class="' . _media_get_list_type() . '">'; 744 } 745 foreach ($data as $item) { 746 if (!$fullscreenview) { 747 //FIXME old call: media_printfile($item,$auth,$jump); 748 $display = new DisplayRow($item); 749 $display->scrollIntoView($jump == $item->getID()); 750 $display->show(); 751 } else { 752 //FIXME old call: media_printfile_thumbs($item,$auth,$jump); 753 echo '<li>'; 754 $display = new DisplayTile($item); 755 $display->scrollIntoView($jump == $item->getID()); 756 $display->show(); 757 echo '</li>'; 758 } 759 } 760 if ($fullscreenview) echo '</ul>'.NL; 761 } 762 } 763} 764 765/** 766 * Prints tabs for files list actions 767 * 768 * @author Kate Arzamastseva <pshns@ukr.net> 769 * @author Adrian Lang <mail@adrianlang.de> 770 * 771 * @param string $selected_tab - opened tab 772 */ 773 774function media_tabs_files($selected_tab = '') 775{ 776 global $lang; 777 $tabs = []; 778 foreach ( 779 [ 780 'files' => 'mediaselect', 781 'upload' => 'media_uploadtab', 782 'search' => 'media_searchtab' 783 ] as $tab => $caption 784 ) { 785 $tabs[$tab] = [ 786 'href' => media_managerURL(['tab_files' => $tab], '&'), 787 'caption' => $lang[$caption] 788 ]; 789 } 790 791 html_tabs($tabs, $selected_tab); 792} 793 794/** 795 * Prints tabs for files details actions 796 * 797 * @author Kate Arzamastseva <pshns@ukr.net> 798 * @param string $image filename of the current image 799 * @param string $selected_tab opened tab 800 */ 801function media_tabs_details($image, $selected_tab = '') 802{ 803 global $lang, $conf; 804 805 $tabs = []; 806 $tabs['view'] = [ 807 'href' => media_managerURL(['tab_details' => 'view'], '&'), 808 'caption' => $lang['media_viewtab'] 809 ]; 810 811 [, $mime] = mimetype($image); 812 if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) { 813 $tabs['edit'] = [ 814 'href' => media_managerURL(['tab_details' => 'edit'], '&'), 815 'caption' => $lang['media_edittab'] 816 ]; 817 } 818 if ($conf['mediarevisions']) { 819 $tabs['history'] = [ 820 'href' => media_managerURL(['tab_details' => 'history'], '&'), 821 'caption' => $lang['media_historytab'] 822 ]; 823 } 824 825 html_tabs($tabs, $selected_tab); 826} 827 828/** 829 * Prints options for the tab that displays a list of all files 830 * 831 * @author Kate Arzamastseva <pshns@ukr.net> 832 */ 833function media_tab_files_options() 834{ 835 global $lang; 836 global $INPUT; 837 global $ID; 838 839 $form = new Form([ 840 'method' => 'get', 841 'action' => wl($ID), 842 'class' => 'options' 843 ]); 844 $form->addTagOpen('div')->addClass('no'); 845 $form->setHiddenField('sectok', null); 846 $media_manager_params = media_managerURL([], '', false, true); 847 foreach ($media_manager_params as $pKey => $pVal) { 848 $form->setHiddenField($pKey, $pVal); 849 } 850 if ($INPUT->has('q')) { 851 $form->setHiddenField('q', $INPUT->str('q')); 852 } 853 $form->addHTML('<ul>'.NL); 854 foreach ( 855 [ 856 'list' => ['listType', ['thumbs', 'rows']], 857 'sort' => ['sortBy', ['name', 'date']] 858 ] as $group => $content 859 ) { 860 $checked = "_media_get_{$group}_type"; 861 $checked = $checked(); 862 863 $form->addHTML('<li class="'. $content[0] .'">'); 864 foreach ($content[1] as $option) { 865 $attrs = []; 866 if ($checked == $option) { 867 $attrs['checked'] = 'checked'; 868 } 869 $radio = $form->addRadioButton( 870 $group.'_dwmedia', 871 $lang['media_'.$group.'_'.$option] 872 )->val($option)->id($content[0].'__'.$option)->addClass($option); 873 $radio->attrs($attrs); 874 } 875 $form->addHTML('</li>'.NL); 876 } 877 $form->addHTML('<li>'); 878 $form->addButton('', $lang['btn_apply'])->attr('type', 'submit'); 879 $form->addHTML('</li>'.NL); 880 $form->addHTML('</ul>'.NL); 881 $form->addTagClose('div'); 882 print $form->toHTML(); 883} 884 885/** 886 * Returns type of sorting for the list of files in media manager 887 * 888 * @author Kate Arzamastseva <pshns@ukr.net> 889 * 890 * @return string - sort type 891 */ 892function _media_get_sort_type() 893{ 894 return _media_get_display_param('sort', ['default' => 'name', 'date']); 895} 896 897/** 898 * Returns type of listing for the list of files in media manager 899 * 900 * @author Kate Arzamastseva <pshns@ukr.net> 901 * 902 * @return string - list type 903 */ 904function _media_get_list_type() 905{ 906 return _media_get_display_param('list', ['default' => 'thumbs', 'rows']); 907} 908 909/** 910 * Get display parameters 911 * 912 * @param string $param name of parameter 913 * @param array $values allowed values, where default value has index key 'default' 914 * @return string the parameter value 915 */ 916function _media_get_display_param($param, $values) 917{ 918 global $INPUT; 919 if (in_array($INPUT->str($param), $values)) { 920 // FIXME: Set cookie 921 return $INPUT->str($param); 922 } else { 923 $val = get_doku_pref($param, $values['default']); 924 if (!in_array($val, $values)) { 925 $val = $values['default']; 926 } 927 return $val; 928 } 929} 930 931/** 932 * Prints tab that displays a list of all files 933 * 934 * @author Kate Arzamastseva <pshns@ukr.net> 935 * 936 * @param string $ns 937 * @param null|int $auth permission level 938 * @param string $jump item id 939 */ 940function media_tab_files($ns, $auth = null, $jump = '') 941{ 942 global $lang; 943 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 944 945 if ($auth < AUTH_READ) { 946 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; 947 } else { 948 media_filelist($ns, $auth, $jump, true, _media_get_sort_type()); 949 } 950} 951 952/** 953 * Prints tab that displays uploading form 954 * 955 * @author Kate Arzamastseva <pshns@ukr.net> 956 * 957 * @param string $ns 958 * @param null|int $auth permission level 959 * @param string $jump item id 960 */ 961function media_tab_upload($ns, $auth = null, $jump = '') 962{ 963 global $lang; 964 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 965 966 echo '<div class="upload">'.NL; 967 if ($auth >= AUTH_UPLOAD) { 968 echo '<p>' . $lang['mediaupload'] . '</p>'; 969 } 970 media_uploadform($ns, $auth, true); 971 echo '</div>'.NL; 972} 973 974/** 975 * Prints tab that displays search form 976 * 977 * @author Kate Arzamastseva <pshns@ukr.net> 978 * 979 * @param string $ns 980 * @param null|int $auth permission level 981 */ 982function media_tab_search($ns, $auth = null) 983{ 984 global $INPUT; 985 986 $do = $INPUT->str('mediado'); 987 $query = $INPUT->str('q'); 988 echo '<div class="search">'.NL; 989 990 media_searchform($ns, $query, true); 991 if ($do == 'searchlist' || $query) { 992 media_searchlist($query, $ns, $auth, true, _media_get_sort_type()); 993 } 994 echo '</div>'.NL; 995} 996 997/** 998 * Prints tab that displays mediafile details 999 * 1000 * @author Kate Arzamastseva <pshns@ukr.net> 1001 * 1002 * @param string $image media id 1003 * @param string $ns 1004 * @param null|int $auth permission level 1005 * @param string|int $rev revision timestamp or empty string 1006 */ 1007function media_tab_view($image, $ns, $auth = null, $rev = '') 1008{ 1009 global $lang; 1010 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 1011 1012 if ($image && $auth >= AUTH_READ) { 1013 $meta = new JpegMeta(mediaFN($image, $rev)); 1014 media_preview($image, $auth, $rev, $meta); 1015 media_preview_buttons($image, $auth, $rev); 1016 media_details($image, $auth, $rev, $meta); 1017 } else { 1018 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; 1019 } 1020} 1021 1022/** 1023 * Prints tab that displays form for editing mediafile metadata 1024 * 1025 * @author Kate Arzamastseva <pshns@ukr.net> 1026 * 1027 * @param string $image media id 1028 * @param string $ns 1029 * @param null|int $auth permission level 1030 */ 1031function media_tab_edit($image, $ns, $auth = null) 1032{ 1033 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 1034 1035 if ($image) { 1036 [, $mime] = mimetype($image); 1037 if ($mime == 'image/jpeg') media_metaform($image, $auth); 1038 } 1039} 1040 1041/** 1042 * Prints tab that displays mediafile revisions 1043 * 1044 * @author Kate Arzamastseva <pshns@ukr.net> 1045 * 1046 * @param string $image media id 1047 * @param string $ns 1048 * @param null|int $auth permission level 1049 */ 1050function media_tab_history($image, $ns, $auth = null) 1051{ 1052 global $lang; 1053 global $INPUT; 1054 1055 if (is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 1056 $do = $INPUT->str('mediado'); 1057 1058 if ($auth >= AUTH_READ && $image) { 1059 if ($do == 'diff') { 1060 (new MediaDiff($image))->show(); //media_diff($image, $ns, $auth); 1061 } else { 1062 $first = $INPUT->int('first', -1); 1063 (new MediaRevisions($image))->show($first); 1064 } 1065 } else { 1066 echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; 1067 } 1068} 1069 1070/** 1071 * Prints mediafile details 1072 * 1073 * @param string $image media id 1074 * @param int $auth permission level 1075 * @param int|string $rev revision timestamp or empty string 1076 * @param JpegMeta|bool $meta 1077 * 1078 * @author Kate Arzamastseva <pshns@ukr.net> 1079 */ 1080function media_preview($image, $auth, $rev = '', $meta = false) 1081{ 1082 1083 $size = media_image_preview_size($image, $rev, $meta); 1084 1085 if ($size) { 1086 global $lang; 1087 echo '<div class="image">'; 1088 1089 $more = []; 1090 if ($rev) { 1091 $more['rev'] = $rev; 1092 } else { 1093 $t = @filemtime(mediaFN($image)); 1094 $more['t'] = $t; 1095 } 1096 1097 $more['w'] = $size[0]; 1098 $more['h'] = $size[1]; 1099 $src = ml($image, $more); 1100 1101 echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">'; 1102 echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />'; 1103 echo '</a>'; 1104 1105 echo '</div>'; 1106 } 1107} 1108 1109/** 1110 * Prints mediafile action buttons 1111 * 1112 * @author Kate Arzamastseva <pshns@ukr.net> 1113 * 1114 * @param string $image media id 1115 * @param int $auth permission level 1116 * @param int|string $rev revision timestamp, or empty string 1117 */ 1118function media_preview_buttons($image, $auth, $rev = '') 1119{ 1120 global $lang, $conf; 1121 1122 echo '<ul class="actions">'; 1123 1124 if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) { 1125 // delete button 1126 $form = new Form([ 1127 'id' => 'mediamanager__btn_delete', 1128 'action' => media_managerURL(['delete' => $image], '&'), 1129 ]); 1130 $form->addTagOpen('div')->addClass('no'); 1131 $form->addButton('', $lang['btn_delete'])->attr('type', 'submit'); 1132 $form->addTagClose('div'); 1133 echo '<li>'; 1134 echo $form->toHTML(); 1135 echo '</li>'; 1136 } 1137 1138 $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE); 1139 if ($auth >= $auth_ow && !$rev) { 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 // restore button 1155 $form = new Form([ 1156 'id' => 'mediamanager__btn_restore', 1157 'action'=>media_managerURL(['image' => $image], '&'), 1158 ]); 1159 $form->addTagOpen('div')->addClass('no'); 1160 $form->setHiddenField('mediado', 'restore'); 1161 $form->setHiddenField('rev', $rev); 1162 $form->addButton('', $lang['media_restore'])->attr('type', 'submit'); 1163 $form->addTagClose('div'); 1164 echo '<li>'; 1165 echo $form->toHTML(); 1166 echo '</li>'; 1167 } 1168 1169 echo '</ul>'; 1170} 1171 1172/** 1173 * Returns image width and height for mediamanager preview panel 1174 * 1175 * @author Kate Arzamastseva <pshns@ukr.net> 1176 * @param string $image 1177 * @param int|string $rev 1178 * @param JpegMeta|bool $meta 1179 * @param int $size 1180 * @return array 1181 */ 1182function media_image_preview_size($image, $rev, $meta = false, $size = 500) 1183{ 1184 if ( 1185 !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 ( 1896 ($mtime == 0) || // cache does not exist 1897 ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired 1898 ) { 1899 if (media_image_download($url, $local)) { 1900 return $local; 1901 } else { 1902 return false; 1903 } 1904 } 1905 1906 //if cache exists use it else 1907 if ($mtime) return $local; 1908 1909 //else return false 1910 return false; 1911} 1912 1913/** 1914 * Download image files 1915 * 1916 * @author Andreas Gohr <andi@splitbrain.org> 1917 * 1918 * @param string $url 1919 * @param string $file path to file in which to put the downloaded content 1920 * @return bool 1921 */ 1922function media_image_download($url, $file) 1923{ 1924 global $conf; 1925 $http = new DokuHTTPClient(); 1926 $http->keep_alive = false; // we do single ops here, no need for keep-alive 1927 1928 $http->max_bodysize = $conf['fetchsize']; 1929 $http->timeout = 25; //max. 25 sec 1930 $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i'; 1931 1932 $data = $http->get($url); 1933 if (!$data) return false; 1934 1935 $fileexists = file_exists($file); 1936 $fp = @fopen($file, "w"); 1937 if (!$fp) return false; 1938 fwrite($fp, $data); 1939 fclose($fp); 1940 if (!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']); 1941 1942 // check if it is really an image 1943 $info = @getimagesize($file); 1944 if (!$info) { 1945 @unlink($file); 1946 return false; 1947 } 1948 1949 return true; 1950} 1951 1952/** 1953 * resize images using external ImageMagick convert program 1954 * 1955 * @author Pavel Vitis <Pavel.Vitis@seznam.cz> 1956 * @author Andreas Gohr <andi@splitbrain.org> 1957 * 1958 * @param string $ext extension 1959 * @param string $from filename path to file 1960 * @param int $from_w original width 1961 * @param int $from_h original height 1962 * @param string $to path to resized file 1963 * @param int $to_w desired width 1964 * @param int $to_h desired height 1965 * @return bool 1966 */ 1967function media_resize_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h) 1968{ 1969 global $conf; 1970 1971 // check if convert is configured 1972 if (!$conf['im_convert']) return false; 1973 1974 // prepare command 1975 $cmd = $conf['im_convert']; 1976 $cmd .= ' -resize '.$to_w.'x'.$to_h.'!'; 1977 if ($ext == 'jpg' || $ext == 'jpeg') { 1978 $cmd .= ' -quality '.$conf['jpg_quality']; 1979 } 1980 $cmd .= " $from $to"; 1981 1982 @exec($cmd, $out, $retval); 1983 if ($retval == 0) return true; 1984 return false; 1985} 1986 1987/** 1988 * crop images using external ImageMagick convert program 1989 * 1990 * @author Andreas Gohr <andi@splitbrain.org> 1991 * 1992 * @param string $ext extension 1993 * @param string $from filename path to file 1994 * @param int $from_w original width 1995 * @param int $from_h original height 1996 * @param string $to path to resized file 1997 * @param int $to_w desired width 1998 * @param int $to_h desired height 1999 * @param int $ofs_x offset of crop centre 2000 * @param int $ofs_y offset of crop centre 2001 * @return bool 2002 * @deprecated 2020-09-01 2003 */ 2004function media_crop_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x, $ofs_y) 2005{ 2006 global $conf; 2007 dbg_deprecated('splitbrain\\Slika'); 2008 2009 // check if convert is configured 2010 if (!$conf['im_convert']) return false; 2011 2012 // prepare command 2013 $cmd = $conf['im_convert']; 2014 $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y; 2015 if ($ext == 'jpg' || $ext == 'jpeg') { 2016 $cmd .= ' -quality '.$conf['jpg_quality']; 2017 } 2018 $cmd .= " $from $to"; 2019 2020 @exec($cmd, $out, $retval); 2021 if ($retval == 0) return true; 2022 return false; 2023} 2024 2025/** 2026 * resize or crop images using PHP's libGD support 2027 * 2028 * @author Andreas Gohr <andi@splitbrain.org> 2029 * @author Sebastian Wienecke <s_wienecke@web.de> 2030 * 2031 * @param string $ext extension 2032 * @param string $from filename path to file 2033 * @param int $from_w original width 2034 * @param int $from_h original height 2035 * @param string $to path to resized file 2036 * @param int $to_w desired width 2037 * @param int $to_h desired height 2038 * @param int $ofs_x offset of crop centre 2039 * @param int $ofs_y offset of crop centre 2040 * @return bool 2041 * @deprecated 2020-09-01 2042 */ 2043function media_resize_imageGD($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x = 0, $ofs_y = 0) 2044{ 2045 global $conf; 2046 dbg_deprecated('splitbrain\\Slika'); 2047 2048 if ($conf['gdlib'] < 1) return false; //no GDlib available or wanted 2049 2050 // check available memory 2051 if (!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))) { 2052 return false; 2053 } 2054 2055 // create an image of the given filetype 2056 $image = false; 2057 if ($ext == 'jpg' || $ext == 'jpeg') { 2058 if (!function_exists("imagecreatefromjpeg")) return false; 2059 $image = @imagecreatefromjpeg($from); 2060 } elseif ($ext == 'png') { 2061 if (!function_exists("imagecreatefrompng")) return false; 2062 $image = @imagecreatefrompng($from); 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, /* srcMime */] = 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 [/* fileExt */, $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