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