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