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