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