You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2240 lines
64 KiB

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