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 9if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/'); 10if(!defined('NL')) define('NL',"\n"); 11 12require_once(DOKU_INC.'inc/html.php'); 13require_once(DOKU_INC.'inc/search.php'); 14require_once(DOKU_INC.'inc/JpegMeta.php'); 15 16/** 17 * Lists pages which currently use a media file selected for deletion 18 * 19 * References uses the same visual as search results and share 20 * their CSS tags except pagenames won't be links. 21 * 22 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 23 */ 24function media_filesinuse($data,$id){ 25 global $lang; 26 echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>'; 27 echo '<p>'.hsc($lang['ref_inuse']).'</p>'; 28 29 $hidden=0; //count of hits without read permission 30 usort($data,'sort_search_fulltext'); 31 foreach($data as $row){ 32 if(auth_quickaclcheck($row['id']) >= AUTH_READ){ 33 echo '<div class="search_result">'; 34 echo '<span class="mediaref_ref">'.$row['id'].'</span>'; 35 echo ': <span class="search_cnt">'.$row['count'].' '.$lang['hits'].'</span><br />'; 36 echo '<div class="search_snippet">'.$row['snippet'].'</div>'; 37 echo '</div>'; 38 }else 39 $hidden++; 40 } 41 if ($hidden){ 42 print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>'; 43 } 44} 45 46/** 47 * Handles the saving of image meta data 48 * 49 * @author Andreas Gohr <andi@splitbrain.org> 50 */ 51function media_metasave($id,$auth,$data){ 52 if($auth < AUTH_UPLOAD) return false; 53 if(!checkSecurityToken()) return false; 54 global $lang; 55 $src = mediaFN($id); 56 57 $meta = new JpegMeta($src); 58 $meta->_parseAll(); 59 60 foreach($data as $key => $val){ 61 $val=trim($val); 62 if(empty($val)){ 63 $meta->deleteField($key); 64 }else{ 65 $meta->setField($key,$val); 66 } 67 } 68 69 if($meta->save()){ 70 msg($lang['metasaveok'],1); 71 return $id; 72 }else{ 73 msg($lang['metasaveerr'],-1); 74 return false; 75 } 76} 77 78/** 79 * Display the form to edit image meta data 80 * 81 * @author Andreas Gohr <andi@splitbrain.org> 82 */ 83function media_metaform($id,$auth){ 84 if($auth < AUTH_UPLOAD) return false; 85 global $lang; 86 87 // load the field descriptions 88 static $fields = null; 89 if(is_null($fields)){ 90 include(DOKU_CONF.'mediameta.php'); 91 if(@file_exists(DOKU_CONF.'mediameta.local.php')){ 92 include(DOKU_CONF.'mediameta.local.php'); 93 } 94 } 95 96 $src = mediaFN($id); 97 98 // output 99 echo '<h1>'.hsc(noNS($id)).'</h1>'.NL; 100 echo '<form action="'.DOKU_BASE.'lib/exe/mediamanager.php" accept-charset="utf-8" method="post" class="meta">'.NL; 101 formSecurityToken(); 102 foreach($fields as $key => $field){ 103 // get current value 104 $tags = array($field[0]); 105 if(is_array($field[3])) $tags = array_merge($tags,$field[3]); 106 $value = tpl_img_getTag($tags,'',$src); 107 108 // prepare attributes 109 $p = array(); 110 $p['class'] = 'edit'; 111 $p['id'] = 'meta__'.$key; 112 $p['name'] = 'meta['.$field[0].']'; 113 114 // put label 115 echo '<div class="metafield">'; 116 echo '<label for="meta__'.$key.'">'; 117 echo ($lang[$field[1]]) ? $lang[$field[1]] : $field[1]; 118 echo ':</label>'; 119 120 // put input field 121 if($field[2] == 'text'){ 122 $p['value'] = $value; 123 $p['type'] = 'text'; 124 $att = buildAttributes($p); 125 echo "<input $att/>".NL; 126 }else{ 127 $att = buildAttributes($p); 128 echo "<textarea $att rows=\"6\" cols=\"50\">".formText($value).'</textarea>'.NL; 129 } 130 echo '</div>'.NL; 131 } 132 echo '<div class="buttons">'.NL; 133 echo '<input type="hidden" name="img" value="'.hsc($id).'" />'.NL; 134 echo '<input name="do[save]" type="submit" value="'.$lang['btn_save']. 135 '" title="ALT+S" accesskey="s" class="button" />'.NL; 136 echo '<input name="do[cancel]" type="submit" value="'.$lang['btn_cancel']. 137 '" title="ALT+C" accesskey="c" class="button" />'.NL; 138 echo '</div>'.NL; 139 echo '</form>'.NL; 140} 141 142/** 143 * Handles media file deletions 144 * 145 * If configured, checks for media references before deletion 146 * 147 * @author Andreas Gohr <andi@splitbrain.org> 148 * @return mixed false on error, true on delete or array with refs 149 */ 150function media_delete($id,$auth){ 151 if($auth < AUTH_DELETE) return false; 152 if(!checkSecurityToken()) return false; 153 global $conf; 154 global $lang; 155 156 $mediareferences = array(); 157 if($conf['refcheck']){ 158 search($mediareferences,$conf['datadir'],'search_reference',array('query' => $id)); 159 } 160 161 if(!count($mediareferences)){ 162 $file = mediaFN($id); 163 if(@unlink($file)){ 164 msg(str_replace('%s',noNS($id),$lang['deletesucc']),1); 165 io_sweepNS($id,'mediadir'); 166 return true; 167 } 168 //something went wrong 169 msg(str_replace('%s',$file,$lang['deletefail']),-1); 170 return false; 171 }elseif(!$conf['refshow']){ 172 msg(str_replace('%s',noNS($id),$lang['mediainuse']),0); 173 return false; 174 } 175 176 return $mediareferences; 177} 178 179/** 180 * Handles media file uploads 181 * 182 * This generates an action event and delegates to _media_upload_action(). 183 * Action plugins are allowed to pre/postprocess the uploaded file. 184 * (The triggered event is preventable.) 185 * 186 * Event data: 187 * $data[0] fn_tmp: the temporary file name (read from $_FILES) 188 * $data[1] fn: the file name of the uploaded file 189 * $data[2] id: the future directory id of the uploaded file 190 * $data[3] imime: the mimetype of the uploaded file 191 * 192 * @triggers MEDIA_UPLOAD_FINISH 193 * @author Andreas Gohr <andi@splitbrain.org> 194 * @author Michael Klier <chi@chimeric.de> 195 * @return mixed false on error, id of the new file on success 196 */ 197function media_upload($ns,$auth){ 198 if($auth < AUTH_UPLOAD) return false; 199 if(!checkSecurityToken()) return false; 200 require_once(DOKU_INC.'inc/confutils.php'); 201 global $lang; 202 global $conf; 203 204 // get file and id 205 $id = $_POST['id']; 206 $file = $_FILES['upload']; 207 if(empty($id)) $id = $file['name']; 208 209 // check extensions 210 list($fext,$fmime) = mimetype($file['name']); 211 list($iext,$imime) = mimetype($id); 212 if($fext && !$iext){ 213 // no extension specified in id - read original one 214 $id .= '.'.$fext; 215 $imime = $fmime; 216 }elseif($fext && $fext != $iext){ 217 // extension was changed, print warning 218 msg(sprintf($lang['mediaextchange'],$fext,$iext)); 219 } 220 221 // get filename 222 $id = cleanID($ns.':'.$id); 223 $fn = mediaFN($id); 224 225 // get filetype regexp 226 $types = array_keys(getMimeTypes()); 227 $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types); 228 $regex = join('|',$types); 229 230 // because a temp file was created already 231 if(preg_match('/\.('.$regex.')$/i',$fn)){ 232 //check for overwrite 233 if(@file_exists($fn) && (!$_POST['ow'] || $auth < AUTH_DELETE)){ 234 msg($lang['uploadexist'],0); 235 return false; 236 } 237 // check for valid content 238 $ok = media_contentcheck($file['tmp_name'],$imime); 239 if($ok == -1){ 240 msg(sprintf($lang['uploadbadcontent'],".$iext"),-1); 241 return false; 242 }elseif($ok == -2){ 243 msg($lang['uploadspam'],-1); 244 return false; 245 }elseif($ok == -3){ 246 msg($lang['uploadxss'],-1); 247 return false; 248 } 249 250 // prepare event data 251 $data[0] = $file['tmp_name']; 252 $data[1] = $fn; 253 $data[2] = $id; 254 $data[3] = $imime; 255 256 // trigger event 257 return trigger_event('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true); 258 259 }else{ 260 msg($lang['uploadwrong'],-1); 261 } 262 return false; 263} 264 265/** 266 * Callback adapter for media_upload_finish() 267 * @author Michael Klier <chi@chimeric.de> 268 */ 269function _media_upload_action($data) { 270 // fixme do further sanity tests of given data? 271 if(is_array($data) && count($data)===4) { 272 return media_upload_finish($data[0], $data[1], $data[2], $data[3]); 273 } else { 274 return false; //callback error 275 } 276} 277 278/** 279 * Saves an uploaded media file 280 * 281 * @author Andreas Gohr <andi@splitbrain.org> 282 * @author Michael Klier <chi@chimeric.de> 283 */ 284function media_upload_finish($fn_tmp, $fn, $id, $imime) { 285 global $conf; 286 global $lang; 287 288 // prepare directory 289 io_createNamespace($id, 'media'); 290 291 if(move_uploaded_file($fn_tmp, $fn)) { 292 // Set the correct permission here. 293 // Always chmod media because they may be saved with different permissions than expected from the php umask. 294 // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.) 295 chmod($fn, $conf['fmode']); 296 msg($lang['uploadsucc'],1); 297 media_notify($id,$fn,$imime); 298 return $id; 299 }else{ 300 msg($lang['uploadfail'],-1); 301 } 302} 303 304/** 305 * This function checks if the uploaded content is really what the 306 * mimetype says it is. We also do spam checking for text types here. 307 * 308 * We need to do this stuff because we can not rely on the browser 309 * to do this check correctly. Yes, IE is broken as usual. 310 * 311 * @author Andreas Gohr <andi@splitbrain.org> 312 * @link http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting 313 * @fixme check all 26 magic IE filetypes here? 314 */ 315function media_contentcheck($file,$mime){ 316 global $conf; 317 if($conf['iexssprotect']){ 318 $fh = @fopen($file, 'rb'); 319 if($fh){ 320 $bytes = fread($fh, 256); 321 fclose($fh); 322 if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i',$bytes)){ 323 return -3; 324 } 325 } 326 } 327 if(substr($mime,0,6) == 'image/'){ 328 $info = @getimagesize($file); 329 if($mime == 'image/gif' && $info[2] != 1){ 330 return -1; 331 }elseif($mime == 'image/jpeg' && $info[2] != 2){ 332 return -1; 333 }elseif($mime == 'image/png' && $info[2] != 3){ 334 return -1; 335 } 336 # fixme maybe check other images types as well 337 }elseif(substr($mime,0,5) == 'text/'){ 338 global $TEXT; 339 $TEXT = io_readFile($file); 340 if(checkwordblock()){ 341 return -2; 342 } 343 } 344 return 0; 345} 346 347/** 348 * Send a notify mail on uploads 349 * 350 * @author Andreas Gohr <andi@splitbrain.org> 351 */ 352function media_notify($id,$file,$mime){ 353 global $lang; 354 global $conf; 355 if(empty($conf['notify'])) return; //notify enabled? 356 357 $text = rawLocale('uploadmail'); 358 $text = str_replace('@DATE@',date($conf['dformat']),$text); 359 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 360 $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text); 361 $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text); 362 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 363 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 364 $text = str_replace('@MIME@',$mime,$text); 365 $text = str_replace('@MEDIA@',ml($id,'',true,'&',true),$text); 366 $text = str_replace('@SIZE@',filesize_h(filesize($file)),$text); 367 368 $from = $conf['mailfrom']; 369 $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from); 370 $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from); 371 $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from); 372 373 $subject = '['.$conf['title'].'] '.$lang['mail_upload'].' '.$id; 374 375 mail_send($conf['notify'],$subject,$text,$from); 376} 377 378/** 379 * List all files in a given Media namespace 380 */ 381function media_filelist($ns,$auth=null,$jump=''){ 382 global $conf; 383 global $lang; 384 $ns = cleanID($ns); 385 386 // check auth our self if not given (needed for ajax calls) 387 if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); 388 389 echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL; 390 391 if($auth < AUTH_READ){ 392 // FIXME: print permission warning here instead? 393 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; 394 return; 395 } 396 397 media_uploadform($ns, $auth); 398 399 $dir = utf8_encodeFN(str_replace(':','/',$ns)); 400 $data = array(); 401 search($data,$conf['mediadir'],'search_media',array(),$dir); 402 403 if(!count($data)){ 404 echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; 405 return; 406 } 407 408 foreach($data as $item){ 409 media_printfile($item,$auth,$jump); 410 } 411} 412 413/** 414 * Print action links for a file depending on filetype 415 * and available permissions 416 * 417 * @todo contains inline javascript 418 */ 419function media_fileactions($item,$auth){ 420 global $lang; 421 422 // view button 423 $link = ml($item['id'],'',true); 424 echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '. 425 'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>'; 426 427 428 // no further actions if not writable 429 if(!$item['writable']) return; 430 431 // delete button 432 if($auth >= AUTH_DELETE){ 433 $ask = addslashes($lang['del_confirm']).'\\n'; 434 $ask .= addslashes($item['id']); 435 436 echo ' <a href="'.DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($item['id']). 437 '&sectok='.getSecurityToken().'" '. 438 'onclick="return confirm(\''.$ask.'\')" onkeypress="return confirm(\''.$ask.'\')">'. 439 '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '. 440 'title="'.$lang['btn_delete'].'" class="btn" /></a>'; 441 } 442 443 // edit button 444 if($auth >= AUTH_UPLOAD && $item['isimg'] && $item['meta']->getField('File.Mime') == 'image/jpeg'){ 445 echo ' <a href="'.DOKU_BASE.'lib/exe/mediamanager.php?edit='.rawurlencode($item['id']).'">'. 446 '<img src="'.DOKU_BASE.'lib/images/pencil.png" alt="'.$lang['metaedit'].'" '. 447 'title="'.$lang['metaedit'].'" class="btn" /></a>'; 448 } 449 450} 451 452/** 453 * Formats and prints one file in the list 454 */ 455function media_printfile($item,$auth,$jump){ 456 global $lang; 457 global $conf; 458 459 // Prepare zebra coloring 460 // I always wanted to use this variable name :-D 461 static $twibble = 1; 462 $twibble *= -1; 463 $zebra = ($twibble == -1) ? 'odd' : 'even'; 464 465 // Automatically jump to recent action 466 if($jump == $item['id']) { 467 $jump = ' id="scroll__here" '; 468 }else{ 469 $jump = ''; 470 } 471 472 // Prepare fileicons 473 list($ext,$mime) = mimetype($item['file']); 474 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 475 $class = 'select mediafile mf_'.$class; 476 477 // Prepare filename 478 $file = utf8_decodeFN($item['file']); 479 480 // Prepare info 481 $info = ''; 482 if($item['isimg']){ 483 $info .= (int) $item['meta']->getField('File.Width'); 484 $info .= '×'; 485 $info .= (int) $item['meta']->getField('File.Height'); 486 $info .= ' '; 487 } 488 $info .= '<i>'.date($conf['dformat'],$item['mtime']).'</i>'; 489 $info .= ' '; 490 $info .= filesize_h($item['size']); 491 492 // ouput 493 echo '<div class="'.$zebra.'"'.$jump.'>'.NL; 494 echo '<a name="h_'.$item['id'].'" class="'.$class.'">'.$file.'</a> '; 495 echo '<span class="info">('.$info.')</span>'.NL; 496 media_fileactions($item,$auth); 497 echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">'; 498 echo $lang['mediausage'].' <code>{{:'.$item['id'].'}}</code>'; 499 echo '</div>'; 500 if($item['isimg']) media_printimgdetail($item); 501 echo '<div class="clearer"></div>'.NL; 502 echo '</div>'.NL; 503} 504 505/** 506 * Prints a thumbnail and metainfos 507 */ 508function media_printimgdetail($item){ 509 // prepare thumbnail 510 $w = (int) $item['meta']->getField('File.Width'); 511 $h = (int) $item['meta']->getField('File.Height'); 512 if($w>120 || $h>120){ 513 $ratio = $item['meta']->getResizeRatio(120); 514 $w = floor($w * $ratio); 515 $h = floor($h * $ratio); 516 } 517 $src = ml($item['id'],array('w'=>$w,'h'=>$h)); 518 $p = array(); 519 $p['width'] = $w; 520 $p['height'] = $h; 521 $p['alt'] = $item['id']; 522 $p['class'] = 'thumb'; 523 $att = buildAttributes($p); 524 525 // output 526 echo '<div class="detail">'; 527 echo '<div class="thumb">'; 528 echo '<a name="d_'.$item['id'].'" class="select">'; 529 echo '<img src="'.$src.'" '.$att.' />'; 530 echo '</a>'; 531 echo '</div>'; 532 533 // read EXIF/IPTC data 534 $t = $item['meta']->getField('IPTC.Headline'); 535 $d = $item['meta']->getField(array('IPTC.Caption','EXIF.UserComment', 536 'EXIF.TIFFImageDescription', 537 'EXIF.TIFFUserComment')); 538 if(utf8_strlen($d) > 250) $d = utf8_substr($d,0,250).'...'; 539 $k = $item['meta']->getField(array('IPTC.Keywords','IPTC.Category')); 540 541 // print EXIF/IPTC data 542 if($t || $d || $k ){ 543 echo '<p>'; 544 if($t) echo '<strong>'.htmlspecialchars($t).'</strong><br />'; 545 if($d) echo htmlspecialchars($d).'<br />'; 546 if($t) echo '<em>'.htmlspecialchars($k).'</em>'; 547 echo '</p>'; 548 } 549 echo '</div>'; 550} 551 552/** 553 * Print the media upload form if permissions are correct 554 * 555 * @author Andreas Gohr <andi@splitbrain.org> 556 */ 557function media_uploadform($ns, $auth){ 558 global $lang; 559 560 if($auth < AUTH_UPLOAD) return; //fixme print info on missing permissions? 561 562 ?> 563 <div class="upload"><?php echo $lang['mediaupload']?></div> 564 <form action="<?php echo DOKU_BASE?>lib/exe/mediamanager.php" 565 method="post" enctype="multipart/form-data" class="upload"> 566 <fieldset> 567 <legend class="hidden"><?php echo $lang['btn_upload']?></legend> 568 <input type="hidden" name="ns" value="<?php echo hsc($ns)?>" /> 569 <?php formSecurityToken();?> 570 <p> 571 <label for="upload__file"><?php echo $lang['txt_upload']?>:</label> 572 <input type="file" name="upload" class="edit" id="upload__file" /> 573 </p> 574 575 <p> 576 <label for="upload__name"><?php echo $lang['txt_filename']?>:</label> 577 <span class="nowrap"> 578 <input type="text" name="id" class="edit" id="upload__name" /><input 579 type="submit" class="button" value="<?php echo $lang['btn_upload']?>" 580 accesskey="s" /> 581 </span> 582 </p> 583 584 <?php if($auth >= AUTH_DELETE){?> 585 <p> 586 <input type="checkbox" name="ow" value="1" id="dw__ow" class="check" /> 587 <label for="dw__ow" class="check"><?php echo $lang['txt_overwrt']?></label> 588 </p> 589 <?php }?> 590 </fieldset> 591 </form> 592 <?php 593} 594 595 596 597/** 598 * Build a tree outline of available media namespaces 599 * 600 * @author Andreas Gohr <andi@splitbrain.org> 601 */ 602function media_nstree($ns){ 603 global $conf; 604 global $lang; 605 606 // currently selected namespace 607 $ns = cleanID($ns); 608 if(empty($ns)){ 609 $ns = dirname(str_replace(':','/',$ID)); 610 if($ns == '.') $ns =''; 611 } 612 $ns = utf8_encodeFN(str_replace(':','/',$ns)); 613 614 $data = array(); 615 search($data,$conf['mediadir'],'search_index',array('ns' => $ns, 'nofiles' => true)); 616 617 // wrap a list with the root level around the other namespaces 618 $item = array( 'level' => 0, 'id' => '', 619 'open' =>'true', 'label' => '['.$lang['mediaroot'].']'); 620 621 echo '<ul class="idx">'; 622 echo media_nstree_li($item); 623 echo media_nstree_item($item); 624 echo html_buildlist($data,'idx','media_nstree_item','media_nstree_li'); 625 echo '</li>'; 626 echo '</ul>'; 627} 628 629/** 630 * Userfunction for html_buildlist 631 * 632 * Prints a media namespace tree item 633 * 634 * @author Andreas Gohr <andi@splitbrain.org> 635 */ 636function media_nstree_item($item){ 637 $pos = strrpos($item['id'], ':'); 638 $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0); 639 if(!$item['label']) $item['label'] = $label; 640 641 $ret = ''; 642 $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">'; 643 $ret .= $item['label']; 644 $ret .= '</a>'; 645 return $ret; 646} 647 648/** 649 * Userfunction for html_buildlist 650 * 651 * Prints a media namespace tree item opener 652 * 653 * @author Andreas Gohr <andi@splitbrain.org> 654 */ 655function media_nstree_li($item){ 656 $class='media level'.$item['level']; 657 if($item['open']){ 658 $class .= ' open'; 659 $img = DOKU_BASE.'lib/images/minus.gif'; 660 $alt = '−'; 661 }else{ 662 $class .= ' closed'; 663 $img = DOKU_BASE.'lib/images/plus.gif'; 664 $alt = '+'; 665 } 666 return '<li class="'.$class.'">'. 667 '<img src="'.$img.'" alt="'.$alt.'" />'; 668} 669