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