[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 10 use dokuwiki\Ui\MediaRevisions; 11 use dokuwiki\Cache\CacheImageMod; 12 use splitbrain\slika\Exception; 13 use dokuwiki\PassHash; 14 use dokuwiki\ChangeLog\MediaChangeLog; 15 use dokuwiki\Extension\Event; 16 use dokuwiki\Form\Form; 17 use dokuwiki\HTTP\DokuHTTPClient; 18 use dokuwiki\Logger; 19 use dokuwiki\Subscriptions\MediaSubscriptionSender; 20 use dokuwiki\Ui\Media\DisplayRow; 21 use dokuwiki\Ui\Media\DisplayTile; 22 use dokuwiki\Ui\MediaDiff; 23 use dokuwiki\Utf8\PhpString; 24 use dokuwiki\Utf8\Sort; 25 use 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 */ 38 function 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 */ 68 function 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 */ 120 function 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 */ 134 function 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 */ 152 function 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 (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 */ 227 function 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 */ 258 function 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 */ 309 function 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 */ 355 function 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 */ 410 function 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 */ 440 function 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 */ 512 function _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 */ 537 function 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 */ 596 function 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 */ 648 function 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 */ 691 function 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 */ 709 function 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 776 function 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 */ 803 function 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 */ 835 function 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 */ 894 function _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 */ 906 function _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 */ 918 function _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 */ 942 function 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 */ 963 function 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 */ 984 function 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 */ 1009 function 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 */ 1033 function 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 */ 1052 function 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 */ 1082 function 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 */ 1120 function 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 */ 1184 function 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 */ 1213 function 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 */ 1229 function 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 */ 1263 function 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 */ 1306 function 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 */ 1318 function _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 */ 1336 function 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 */ 1355 function 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 */ 1370 function 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 */ 1407 function 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 */ 1481 function 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 */ 1505 function 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 */ 1540 function 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 */ 1613 function 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 */ 1638 function 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 */ 1671 function 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 foreach ($ns_parts as $level => $part) { 1696 if ($tmp_ns) $tmp_ns .= ':' . $part; 1697 else $tmp_ns = $part; 1698 1699 // find the namespace parts or insert them 1700 while ($data[$pos]['id'] != $tmp_ns) { 1701 if ( 1702 $pos >= count($data) || 1703 ($data[$pos]['level'] <= $level + 1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0) 1704 ) { 1705 array_splice($data, $pos, 0, [['level' => $level + 1, 'id' => $tmp_ns, 'open' => 'true']]); 1706 break; 1707 } 1708 ++$pos; 1709 } 1710 } 1711 1712 echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li'); 1713 } 1714 1715 /** 1716 * Userfunction for html_buildlist 1717 * 1718 * Prints a media namespace tree item 1719 * 1720 * @author Andreas Gohr <andi@splitbrain.org> 1721 * 1722 * @param array $item 1723 * @return string html 1724 */ 1725 function media_nstree_item($item) 1726 { 1727 global $INPUT; 1728 $pos = strrpos($item['id'], ':'); 1729 $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0); 1730 if (empty($item['label'])) $item['label'] = $label; 1731 1732 $ret = ''; 1733 if ($INPUT->str('do') != 'media') 1734 $ret .= '<a href="' . DOKU_BASE . 'lib/exe/mediamanager.php?ns=' . idfilter($item['id']) . '" class="idx_dir">'; 1735 else $ret .= '<a href="' . media_managerURL(['ns' => idfilter($item['id'], false), 'tab_files' => 'files']) 1736 . '" class="idx_dir">'; 1737 $ret .= $item['label']; 1738 $ret .= '</a>'; 1739 return $ret; 1740 } 1741 1742 /** 1743 * Userfunction for html_buildlist 1744 * 1745 * Prints a media namespace tree item opener 1746 * 1747 * @author Andreas Gohr <andi@splitbrain.org> 1748 * 1749 * @param array $item 1750 * @return string html 1751 */ 1752 function media_nstree_li($item) 1753 { 1754 $class = 'media level' . $item['level']; 1755 if ($item['open']) { 1756 $class .= ' open'; 1757 $img = DOKU_BASE . 'lib/images/minus.gif'; 1758 $alt = '−'; 1759 } else { 1760 $class .= ' closed'; 1761 $img = DOKU_BASE . 'lib/images/plus.gif'; 1762 $alt = '+'; 1763 } 1764 // TODO: only deliver an image if it actually has a subtree... 1765 return '<li class="' . $class . '">' . 1766 '<img src="' . $img . '" alt="' . $alt . '" />'; 1767 } 1768 1769 /** 1770 * Resizes or crop the given image to the given size 1771 * 1772 * @author Andreas Gohr <andi@splitbrain.org> 1773 * 1774 * @param string $file filename, path to file 1775 * @param string $ext extension 1776 * @param int $w desired width 1777 * @param int $h desired height 1778 * @param bool $crop should a center crop be used? 1779 * @return string path to resized or original size if failed 1780 */ 1781 function media_mod_image($file, $ext, $w, $h = 0, $crop = false) 1782 { 1783 global $conf; 1784 if (!$h) $h = 0; 1785 // we wont scale up to infinity 1786 if ($w > 2000 || $h > 2000) return $file; 1787 1788 $operation = $crop ? 'crop' : 'resize'; 1789 1790 $options = [ 1791 'quality' => $conf['jpg_quality'], 1792 'imconvert' => $conf['im_convert'], 1793 ]; 1794 1795 $cache = new CacheImageMod($file, $w, $h, $ext, $crop); 1796 if (!$cache->useCache()) { 1797 try { 1798 Slika::run($file, $options) 1799 ->autorotate() 1800 ->$operation($w, $h) 1801 ->save($cache->cache, $ext); 1802 if ($conf['fperm']) @chmod($cache->cache, $conf['fperm']); 1803 } catch (Exception $e) { 1804 Logger::debug($e->getMessage()); 1805 return $file; 1806 } 1807 } 1808 1809 return $cache->cache; 1810 } 1811 1812 /** 1813 * Resizes the given image to the given size 1814 * 1815 * @author Andreas Gohr <andi@splitbrain.org> 1816 * 1817 * @param string $file filename, path to file 1818 * @param string $ext extension 1819 * @param int $w desired width 1820 * @param int $h desired height 1821 * @return string path to resized or original size if failed 1822 */ 1823 function media_resize_image($file, $ext, $w, $h = 0) 1824 { 1825 return media_mod_image($file, $ext, $w, $h, false); 1826 } 1827 1828 /** 1829 * Center crops the given image to the wanted size 1830 * 1831 * @author Andreas Gohr <andi@splitbrain.org> 1832 * 1833 * @param string $file filename, path to file 1834 * @param string $ext extension 1835 * @param int $w desired width 1836 * @param int $h desired height 1837 * @return string path to resized or original size if failed 1838 */ 1839 function media_crop_image($file, $ext, $w, $h = 0) 1840 { 1841 return media_mod_image($file, $ext, $w, $h, true); 1842 } 1843 1844 /** 1845 * Calculate a token to be used to verify fetch requests for resized or 1846 * cropped images have been internally generated - and prevent external 1847 * DDOS attacks via fetch 1848 * 1849 * @author Christopher Smith <chris@jalakai.co.uk> 1850 * 1851 * @param string $id id of the image 1852 * @param int $w resize/crop width 1853 * @param int $h resize/crop height 1854 * @return string token or empty string if no token required 1855 */ 1856 function media_get_token($id, $w, $h) 1857 { 1858 // token is only required for modified images 1859 if ($w || $h || media_isexternal($id)) { 1860 $token = $id; 1861 if ($w) $token .= '.' . $w; 1862 if ($h) $token .= '.' . $h; 1863 1864 return substr(PassHash::hmac('md5', $token, auth_cookiesalt()), 0, 6); 1865 } 1866 1867 return ''; 1868 } 1869 1870 /** 1871 * Download a remote file and return local filename 1872 * 1873 * returns false if download fails. Uses cached file if available and 1874 * wanted 1875 * 1876 * @author Andreas Gohr <andi@splitbrain.org> 1877 * @author Pavel Vitis <Pavel.Vitis@seznam.cz> 1878 * 1879 * @param string $url 1880 * @param string $ext extension 1881 * @param int $cache cachetime in seconds 1882 * @return false|string path to cached file 1883 */ 1884 function media_get_from_URL($url, $ext, $cache) 1885 { 1886 global $conf; 1887 1888 // if no cache or fetchsize just redirect 1889 if ($cache == 0) return false; 1890 if (!$conf['fetchsize']) return false; 1891 1892 $local = getCacheName(strtolower($url), ".media.$ext"); 1893 $mtime = @filemtime($local); // 0 if not exists 1894 1895 //decide if download needed: 1896 if ( 1897 ($mtime == 0) || // cache does not exist 1898 ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired 1899 ) { 1900 if (media_image_download($url, $local)) { 1901 return $local; 1902 } else { 1903 return false; 1904 } 1905 } 1906 1907 //if cache exists use it else 1908 if ($mtime) return $local; 1909 1910 //else return false 1911 return false; 1912 } 1913 1914 /** 1915 * Download image files 1916 * 1917 * @author Andreas Gohr <andi@splitbrain.org> 1918 * 1919 * @param string $url 1920 * @param string $file path to file in which to put the downloaded content 1921 * @return bool 1922 */ 1923 function media_image_download($url, $file) 1924 { 1925 global $conf; 1926 $http = new DokuHTTPClient(); 1927 $http->keep_alive = false; // we do single ops here, no need for keep-alive 1928 1929 $http->max_bodysize = $conf['fetchsize']; 1930 $http->timeout = 25; //max. 25 sec 1931 $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i'; 1932 1933 $data = $http->get($url); 1934 if (!$data) return false; 1935 1936 $fileexists = file_exists($file); 1937 $fp = @fopen($file, "w"); 1938 if (!$fp) return false; 1939 fwrite($fp, $data); 1940 fclose($fp); 1941 if (!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']); 1942 1943 // check if it is really an image 1944 $info = @getimagesize($file); 1945 if (!$info) { 1946 @unlink($file); 1947 return false; 1948 } 1949 1950 return true; 1951 } 1952 1953 /** 1954 * resize images using external ImageMagick convert program 1955 * 1956 * @author Pavel Vitis <Pavel.Vitis@seznam.cz> 1957 * @author Andreas Gohr <andi@splitbrain.org> 1958 * 1959 * @param string $ext extension 1960 * @param string $from filename path to file 1961 * @param int $from_w original width 1962 * @param int $from_h original height 1963 * @param string $to path to resized file 1964 * @param int $to_w desired width 1965 * @param int $to_h desired height 1966 * @return bool 1967 */ 1968 function media_resize_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h) 1969 { 1970 global $conf; 1971 1972 // check if convert is configured 1973 if (!$conf['im_convert']) return false; 1974 1975 // prepare command 1976 $cmd = $conf['im_convert']; 1977 $cmd .= ' -resize ' . $to_w . 'x' . $to_h . '!'; 1978 if ($ext == 'jpg' || $ext == 'jpeg') { 1979 $cmd .= ' -quality ' . $conf['jpg_quality']; 1980 } 1981 $cmd .= " $from $to"; 1982 1983 @exec($cmd, $out, $retval); 1984 if ($retval == 0) return true; 1985 return false; 1986 } 1987 1988 /** 1989 * crop images using external ImageMagick convert program 1990 * 1991 * @author Andreas Gohr <andi@splitbrain.org> 1992 * 1993 * @param string $ext extension 1994 * @param string $from filename path to file 1995 * @param int $from_w original width 1996 * @param int $from_h original height 1997 * @param string $to path to resized file 1998 * @param int $to_w desired width 1999 * @param int $to_h desired height 2000 * @param int $ofs_x offset of crop centre 2001 * @param int $ofs_y offset of crop centre 2002 * @return bool 2003 * @deprecated 2020-09-01 2004 */ 2005 function media_crop_imageIM($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x, $ofs_y) 2006 { 2007 global $conf; 2008 dbg_deprecated('splitbrain\\Slika'); 2009 2010 // check if convert is configured 2011 if (!$conf['im_convert']) return false; 2012 2013 // prepare command 2014 $cmd = $conf['im_convert']; 2015 $cmd .= ' -crop ' . $to_w . 'x' . $to_h . '+' . $ofs_x . '+' . $ofs_y; 2016 if ($ext == 'jpg' || $ext == 'jpeg') { 2017 $cmd .= ' -quality ' . $conf['jpg_quality']; 2018 } 2019 $cmd .= " $from $to"; 2020 2021 @exec($cmd, $out, $retval); 2022 if ($retval == 0) return true; 2023 return false; 2024 } 2025 2026 /** 2027 * resize or crop images using PHP's libGD support 2028 * 2029 * @author Andreas Gohr <andi@splitbrain.org> 2030 * @author Sebastian Wienecke <s_wienecke@web.de> 2031 * 2032 * @param string $ext extension 2033 * @param string $from filename path to file 2034 * @param int $from_w original width 2035 * @param int $from_h original height 2036 * @param string $to path to resized file 2037 * @param int $to_w desired width 2038 * @param int $to_h desired height 2039 * @param int $ofs_x offset of crop centre 2040 * @param int $ofs_y offset of crop centre 2041 * @return bool 2042 * @deprecated 2020-09-01 2043 */ 2044 function media_resize_imageGD($ext, $from, $from_w, $from_h, $to, $to_w, $to_h, $ofs_x = 0, $ofs_y = 0) 2045 { 2046 global $conf; 2047 dbg_deprecated('splitbrain\\Slika'); 2048 2049 if ($conf['gdlib'] < 1) return false; //no GDlib available or wanted 2050 2051 // check available memory 2052 if (!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))) { 2053 return false; 2054 } 2055 2056 // create an image of the given filetype 2057 $image = false; 2058 if ($ext == 'jpg' || $ext == 'jpeg') { 2059 if (!function_exists("imagecreatefromjpeg")) return false; 2060 $image = @imagecreatefromjpeg($from); 2061 } elseif ($ext == 'png') { 2062 if (!function_exists("imagecreatefrompng")) return false; 2063 $image = @imagecreatefrompng($from); 2064 } elseif ($ext == 'gif') { 2065 if (!function_exists("imagecreatefromgif")) return false; 2066 $image = @imagecreatefromgif($from); 2067 } 2068 if (!$image) return false; 2069 2070 $newimg = false; 2071 if (($conf['gdlib'] > 1) && function_exists("imagecreatetruecolor") && $ext != 'gif') { 2072 $newimg = @imagecreatetruecolor($to_w, $to_h); 2073 } 2074 if (!$newimg) $newimg = @imagecreate($to_w, $to_h); 2075 if (!$newimg) { 2076 imagedestroy($image); 2077 return false; 2078 } 2079 2080 //keep png alpha channel if possible 2081 if ($ext == 'png' && $conf['gdlib'] > 1 && function_exists('imagesavealpha')) { 2082 imagealphablending($newimg, false); 2083 imagesavealpha($newimg, true); 2084 } 2085 2086 //keep gif transparent color if possible 2087 if ($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) { 2088 if (function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) { 2089 $transcolorindex = @imagecolortransparent($image); 2090 if ($transcolorindex >= 0) { //transparent color exists 2091 $transcolor = @imagecolorsforindex($image, $transcolorindex); 2092 $transcolorindex = @imagecolorallocate( 2093 $newimg, 2094 $transcolor['red'], 2095 $transcolor['green'], 2096 $transcolor['blue'] 2097 ); 2098 @imagefill($newimg, 0, 0, $transcolorindex); 2099 @imagecolortransparent($newimg, $transcolorindex); 2100 } else { //filling with white 2101 $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255); 2102 @imagefill($newimg, 0, 0, $whitecolorindex); 2103 } 2104 } else { //filling with white 2105 $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255); 2106 @imagefill($newimg, 0, 0, $whitecolorindex); 2107 } 2108 } 2109 2110 //try resampling first 2111 if (function_exists("imagecopyresampled")) { 2112 if (!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) { 2113 imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h); 2114 } 2115 } else { 2116 imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h); 2117 } 2118 2119 $okay = false; 2120 if ($ext == 'jpg' || $ext == 'jpeg') { 2121 if (!function_exists('imagejpeg')) { 2122 $okay = false; 2123 } else { 2124 $okay = imagejpeg($newimg, $to, $conf['jpg_quality']); 2125 } 2126 } elseif ($ext == 'png') { 2127 if (!function_exists('imagepng')) { 2128 $okay = false; 2129 } else { 2130 $okay = imagepng($newimg, $to); 2131 } 2132 } elseif ($ext == 'gif') { 2133 if (!function_exists('imagegif')) { 2134 $okay = false; 2135 } else { 2136 $okay = imagegif($newimg, $to); 2137 } 2138 } 2139 2140 // destroy GD image resources 2141 imagedestroy($image); 2142 imagedestroy($newimg); 2143 2144 return $okay; 2145 } 2146 2147 /** 2148 * Return other media files with the same base name 2149 * but different extensions. 2150 * 2151 * @param string $src - ID of media file 2152 * @param string[] $exts - alternative extensions to find other files for 2153 * @return array - array(mime type => file ID) 2154 * 2155 * @author Anika Henke <anika@selfthinker.org> 2156 */ 2157 function media_alternativefiles($src, $exts) 2158 { 2159 2160 $files = []; 2161 [$srcExt, /* srcMime */] = mimetype($src); 2162 $filebase = substr($src, 0, -1 * (strlen($srcExt) + 1)); 2163 2164 foreach ($exts as $ext) { 2165 $fileid = $filebase . '.' . $ext; 2166 $file = mediaFN($fileid); 2167 if (file_exists($file)) { 2168 [/* fileExt */, $fileMime] = mimetype($file); 2169 $files[$fileMime] = $fileid; 2170 } 2171 } 2172 return $files; 2173 } 2174 2175 /** 2176 * Check if video/audio is supported to be embedded. 2177 * 2178 * @param string $mime - mimetype of media file 2179 * @param string $type - type of media files to check ('video', 'audio', or null for all) 2180 * @return boolean 2181 * 2182 * @author Anika Henke <anika@selfthinker.org> 2183 */ 2184 function media_supportedav($mime, $type = null) 2185 { 2186 $supportedAudio = [ 2187 'ogg' => 'audio/ogg', 2188 'mp3' => 'audio/mpeg', 2189 'wav' => 'audio/wav' 2190 ]; 2191 $supportedVideo = [ 2192 'webm' => 'video/webm', 2193 'ogv' => 'video/ogg', 2194 'mp4' => 'video/mp4' 2195 ]; 2196 if ($type == 'audio') { 2197 $supportedAv = $supportedAudio; 2198 } elseif ($type == 'video') { 2199 $supportedAv = $supportedVideo; 2200 } else { 2201 $supportedAv = array_merge($supportedAudio, $supportedVideo); 2202 } 2203 return in_array($mime, $supportedAv); 2204 } 2205 2206 /** 2207 * Return track media files with the same base name 2208 * but extensions that indicate kind and lang. 2209 * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt... 2210 * 2211 * @param string $src - ID of media file 2212 * @return array - array(mediaID => array( kind, srclang )) 2213 * 2214 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 2215 */ 2216 function media_trackfiles($src) 2217 { 2218 $kinds = [ 2219 'sub' => 'subtitles', 2220 'cap' => 'captions', 2221 'des' => 'descriptions', 2222 'cha' => 'chapters', 2223 'met' => 'metadata' 2224 ]; 2225 2226 $files = []; 2227 $re = '/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/'; 2228 $baseid = pathinfo($src, PATHINFO_FILENAME); 2229 $pattern = mediaFN($baseid) . '.*.*.vtt'; 2230 $list = glob($pattern); 2231 foreach ($list as $track) { 2232 if (preg_match($re, $track, $matches)) { 2233 $files[$baseid . '.' . $matches[1] . '.' . $matches[2] . '.vtt'] = [$kinds[$matches[1]], $matches[2]]; 2234 } 2235 } 2236 return $files; 2237 } 2238 2239 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body