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