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