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