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