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