[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> media.php (source)

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