00001 <?php 00018 // Import classes 00019 ClassFactory::importClass('PageServer'); 00020 ClassFactory::importClass('FbScanDir'); 00021 ClassFactory::importClass('FbFileType'); 00022 00027 class FileBrowser { 00032 public static function requested() { 00033 return @$_GET['show']=='FileBrowser'; 00034 } 00039 public static function handleRequest() { 00040 if (!self::requested()) 00041 return false; 00042 00043 // Direct access to files placed in special folder ($page/html by default) 00044 if (!isset(self::$instance_) && @$_GET['action']=='directAccess') { 00045 self::$instance_ = new FileBrowser(Config::instance()->htmlDir()); 00046 self::instance()->actionDirectAccess(); 00047 return true; 00048 } 00049 00050 // Execute PHP script placed in special folder ($page/php by default) 00051 if (!isset(self::$instance_) && @$_GET['action']=='execPHP') { 00052 self::$instance_ = new FileBrowser(Config::instance()->execPhpDir()); 00053 self::instance()->actionExecPhp(); 00054 return true; 00055 } 00056 00057 // Create FileBrowser instance 00058 $obj = self::instance(); 00059 switch (@$_GET['action']) { 00060 case 'download': 00061 $obj->actionDownload(); 00062 return true; 00063 case 'downloadUTF8': 00064 $obj->actionDownloadUTF8(); 00065 return true; 00066 case 'thumbnail640': 00067 $obj->actionThumbnail(640); 00068 return true; 00069 case 'thumbnail320': 00070 $obj->actionThumbnail(320); 00071 return true; 00072 case 'thumbnail160': 00073 $obj->actionThumbnail(160); 00074 return true; 00075 case 'fullImage': 00076 $obj->actionFullImage(); 00077 return true; 00078 case 'sendPdf': 00079 $obj->actionSendPdf(); 00080 return true; 00081 case 'asyncDispatch': 00082 $obj->asyncDispatch(); 00083 return true; 00084 case 'browse': 00085 default: 00086 return false; 00087 } 00088 } 00089 00094 public static function titlePostfix() { 00095 if (!self::requested()) 00096 return ''; 00097 else 00098 return ' - '.self::instance()->contentTitle(); 00099 } 00100 00107 public static function showContent($page, $pageTitle) { 00108 $obj = self::instance(); 00109 $obj->page_ = $page; 00110 $obj->pageTitle_ = $pageTitle; 00111 00112 if ($obj->isDir()) 00113 $obj->viewFileBrowser(); 00114 else 00115 $obj->viewFileDetail(); 00116 } 00117 00122 public static function genHeadIfNeeded() { 00123 if (!self::requested()) 00124 return false; 00125 $obj = self::instance(); 00126 if ($obj->isDir()) 00127 $obj->genHead(); 00128 return true; 00129 } 00130 00131 private static $instance_; 00135 private static function instance() { 00136 if (!isset(self::$instance_)) 00137 self::$instance_ = new FileBrowser; 00138 return self::$instance_; 00139 } 00140 00141 // Local data 00142 private $l10n_; 00143 private $page_; 00144 private $pageTitle_; 00145 private $smarty_; 00146 00147 // Read-only properties (private for now) 00148 private $dirDocRoot_; private function dirDocRoot() { return $this->dirDocRoot_; } 00149 private $dirWebRoot_; private function dirWebRoot() { return $this->dirWebRoot_; } 00150 private $path_; private function path() { return $this->path_; } 00151 private $fullPath_; private function fullPath() { return $this->fullPath_; } 00152 00157 private function __construct($branch=null) { 00158 $config = Config::instance(); 00159 $docRoot = $config->documentRoot(); 00160 $webRoot = $config->webRoot(); 00161 00162 if (!isset($branch)) 00163 $branch = $config->filesDir(); 00164 00165 $page = @$_GET['page']; 00166 if (!PageServer::isPageValid($page)) 00167 throw new ExceptionNotFound; 00168 00169 $this->dirDocRoot_ = realpath($docRoot.$branch.'/'.$page); 00170 if (false===$this->dirDocRoot_) 00171 throw new ExceptionNotFound($docRoot.$branch.'/'.$page); 00172 if (!is_dir($this->dirDocRoot_)) 00173 // Files root directory is not directory 00174 throw new ExceptionNotFound($this->dirDocRoot_); 00175 00176 $this->dirWebRoot_ = $webRoot.'/'.$page.$branch; 00177 00178 $path = @$_GET['path']; 00179 $this->fullPath_ = realpath($this->dirDocRoot_.'/'.$path); 00180 if (false===$this->fullPath_) 00181 // Relative path is not valid 00182 throw new ExceptionNotFound($this->dirDocRoot_.'/'.$path); 00183 00184 if (!file_exists($this->fullPath_)) 00185 // Another check (needed for hosting on Blueboard) 00186 throw new ExceptionNotFound($this->fullPath_); 00187 00188 if (0!==strpos($this->fullPath_, $this->dirDocRoot_)) 00189 // Possible attack 00190 throw new ExceptionNotFound(realpath($this->fullPath_)); 00191 00192 // FIXME: Not tested yet 00193 $path = str_replace($this->dirDocRoot_, '', $this->fullPath_); 00194 $path = ereg_replace('^\/*', '', $path); 00195 $path = ereg_replace('\/*$', '', $path); 00196 $this->path_ = $path; 00197 00198 // FileBrowser Localization 00199 // TODO: Define l10n outside FileBrowser? 00200 $this->l10n_ = new L10n; 00201 $this->l10n_->setTranslation('cz', 'Ascending', 'Vzestupně'); 00202 $this->l10n_->setTranslation('cz', 'Back', 'Zpět'); 00203 $this->l10n_->setTranslation('cz', 'BackToDirectory', 'Přejít do složky'); 00204 $this->l10n_->setTranslation('en', 'BackToDirectory', 'Go to directory'); 00205 $this->l10n_->setTranslation('cz', 'BackToMainPage', 'Zpět na hlavní stránku'); 00206 $this->l10n_->setTranslation('en', 'BackToMainPage', 'Back to main page'); 00207 $this->l10n_->setTranslation('cz', 'Descending', 'Sestupně'); 00208 $this->l10n_->setTranslation('en', 'DetectedCharset', 'Detected charset'); 00209 $this->l10n_->setTranslation('cz', 'DetectedCharset', 'Detekovaná znaková sada'); 00210 $this->l10n_->setTranslation('cz', 'Download', 'Stáhnout'); 00211 $this->l10n_->setTranslation('en', 'DownloadAsUTF8', 'Download as UTF-8'); 00212 $this->l10n_->setTranslation('cz', 'DownloadAsUTF8', 'Stáhnout jako UTF-8'); 00213 $this->l10n_->setTranslation('cz', 'Directory', 'Adresář'); 00214 $this->l10n_->setTranslation('cz', 'File', 'Soubor'); 00215 $this->l10n_->setTranslation('en', 'FileBrowser', 'File browser'); 00216 $this->l10n_->setTranslation('cz', 'FileBrowser', 'Prohlížeč souborů'); 00217 $this->l10n_->setTranslation('en', 'FileContent', 'File content'); 00218 $this->l10n_->setTranslation('cz', 'FileContent', 'Obsah souboru'); 00219 $this->l10n_->setTranslation('en', 'FileDetail', 'File detail'); 00220 $this->l10n_->setTranslation('cz', 'FileDetail', 'Detail souboru'); 00221 $this->l10n_->setTranslation('cz', 'Full size', 'Plná velikost'); 00222 $this->l10n_->setTranslation('cz', 'Location', 'Umístění'); 00223 $this->l10n_->setTranslation('en', 'Mtime', 'Last modification'); 00224 $this->l10n_->setTranslation('cz', 'Mtime', 'Poslední změna'); 00225 $this->l10n_->setTranslation('cz', 'Name', 'Jméno'); 00226 $this->l10n_->setTranslation('cz', 'Open', 'Otevřít'); 00227 $this->l10n_->setTranslation('cz', 'Preview', 'Náhled'); 00228 $this->l10n_->setTranslation('cz', 'Size', 'Velikost'); 00229 $this->l10n_->setTranslation('en', 'SourceCode', 'Source code'); 00230 $this->l10n_->setTranslation('cz', 'SourceCode', 'Zdrojový kód'); 00231 $this->l10n_->setTranslation('cz', 'View', 'Zobrazit'); 00232 } 00233 00237 private function contentTitle() { 00238 $contentType = $this->isDir()?'Directory':'File'; 00239 return $this->l10n_->tr($contentType).': /'.$this->path_; 00240 } 00241 00245 private function isDir() { 00246 return is_dir($this->fullPath_); 00247 } 00248 00252 private function initSmarty() { 00253 $this->smarty_ = SmartyFactory::createSmarty(); 00254 $this->smarty_->assign('TR', $this->l10n_->trMap()); 00255 $this->smarty_->assign('page', $this->page_); 00256 $this->smarty_->assign('pageTitle', $this->pageTitle_); 00257 $this->smarty_->assign('imgCollapsed', 'tree_plus.png'); 00258 $this->smarty_->assign('rowClasses', Array('odd', 'even')); 00259 } 00260 00264 private function initLocationBar() { 00265 $fullPath = $this->dirDocRoot().'/'.$this->path(); 00266 $dir = realpath($fullPath); 00267 if (false===$dir) 00268 throw new ExceptionNotFound($fullPath); 00269 if (!is_dir($dir)) 00270 $dir = dirname($dir); 00271 00272 // Parse path from end and find directories (in reverse order) 00273 $pathStack = Array(); 00274 while ($this->dirDocRoot() != $dir) { 00275 $lastDir = $dir; 00276 $dir = realpath($dir.'/..'); 00277 $href = str_replace($this->dirDocRoot(), $this->dirWebRoot(), $lastDir); 00278 $text = str_replace($dir.'/', '', $lastDir); 00279 $pathStack []= Array( 00280 'href' => $href, 00281 'text' => $text); 00282 } 00283 00284 // Add link to root 00285 $pathStack []= Array( 00286 'href' => $this->dirWebRoot().'/', 00287 'text' => $this->page_); 00288 00289 // Reverse array and put to local Smarty instance 00290 $nav = array_reverse($pathStack); 00291 $this->smarty_->assign('locationBar', $nav); 00292 } 00293 00298 private function buildFileList($bLinkToParent) { 00299 $path = $this->path_; 00300 00301 // Get file list and sort 00302 $scanDir = new FbScanDir($this->fullPath_); 00303 $scanDir->setLocale($this->l10n_); 00304 $this->smarty_->assign('sort', $scanDir->sortLinkArray()); 00305 $dirList = $scanDir->dirList(); 00306 $fileList = $scanDir->fileList(); 00307 00308 if ($bLinkToParent && realpath($this->dirDocRoot_)!=realpath($this->fullPath_)) 00309 // Add link to parent 00310 array_unshift($dirList, Array('name'=>'..')); 00311 00312 // Directory-only decorations 00313 foreach ($dirList as &$current) { 00314 $name = $current['name']; 00315 $current['name'] = Array('text'=>$name); 00316 $fileLink = $this->dirWebRoot_.'/'.$path.(empty($path)?'':'/').$name; 00317 $current['name']['href']= $fileLink; 00318 if ('..'==$name) { 00319 $current['name']['text'] = '[..]'; 00320 continue; 00321 } 00322 $current['treeImg'] = Array( 00323 'src' => 'tree_plus.png', 00324 'alt' => '+', 00325 'class' => 'imgCollapsed'); 00326 $current['actions'] = Array( 00327 Array( 00328 'href' => $fileLink.'?action=download', 00329 'text' => self::instance()->l10n_->tr('Download'), 00330 'img' => 'download_dir.png', 00331 'default' => false), 00332 // Array( 00333 // 'href' => $fileLink, 00334 // 'text' => self::instance()->l10n_->tr('Open'), 00335 // 'default' => true), 00336 ); 00337 } 00338 00339 // File-only decorations 00340 foreach ($fileList as &$current) { 00341 $name = $current['name']; 00342 $ft = new FbFileType($this->dirDocRoot_.'/'.$path.'/'.$name); 00343 $current['name'] = Array('text'=>$name); 00344 $fileLink = $this->dirWebRoot_.'/'.$path.(empty($path)?'':'/').$name; 00345 $current['name']['href']= $fileLink; 00346 if ($ft->isDownloadDefault()) 00347 $current['name']['href'] .= '?action=download'; 00348 else if ($ft->isPdf()) 00349 $current['name']['href'] .= '?action=sendPdf'; 00350 $current['size'] = FbScanDir::sizeToString($current['size']); 00351 $current['actions'] = Array( 00352 Array( 00353 'href' => $fileLink.'?action=download', 00354 'text' => self::instance()->l10n_->tr('Download'), 00355 'img' => 'download.png', 00356 'default' => $ft->isDownloadDefault())); 00357 if ($ft->isTextFile() || $ft->isPdf()) 00358 $current['actions'] []= Array( 00359 'href' => $current['name']['href'], 00360 'text' => self::instance()->l10n_->tr('View'), 00361 'default' => true); 00362 if ($ft->isImage()) { 00363 $current['actions'] []= Array( 00364 'href' => $fileLink, 00365 'text' => self::instance()->l10n_->tr('Preview'), 00366 'default' => true); 00367 $current['actions'] []= Array( 00368 'href' => 'javascript:void window.open(\''.$fileLink.'?action=fullImage\');', 00369 'text' => self::instance()->l10n_->tr('Full size'), 00370 'default' => false); 00371 } 00372 $current['treeImg'] = Array( 00373 'src' => 'tree_item.png', 00374 'alt' => ''); 00375 } 00376 00377 // Build common (dir/file)list 00378 $all = array_merge($dirList, $fileList); 00379 00380 // Common (dir/file) decorations 00381 foreach ($all as &$current) { 00382 if (array_key_exists('mtime', $current)) { 00383 $current['date'] = FbScanDir::mtimeDateToSTring($current['mtime']); 00384 $current['time'] = FbScanDir::mtimeTimeToSTring($current['mtime']); 00385 } 00386 } 00387 $this->smarty_->assign('fileList', $all); 00388 } 00389 00393 private function viewFileBrowser() { 00394 $this->initSmarty(); 00395 $this->initLocationBar(); 00396 $this->buildFileList(true); 00397 $this->smarty_->display('FileBrowser.tpl'); 00398 } 00399 00403 private function viewFileDetail() { 00404 // Initialize smarty 00405 $this->initSmarty(); 00406 $this->initLocationBar(); 00407 00408 // Get information 00409 $name = basename($this->fullPath_); 00410 $size = filesize($this->fullPath_); 00411 $mtime = filemtime($this->fullPath_); 00412 $dirLink = str_replace($this->dirDocRoot_, $this->dirWebRoot_, dirname($this->fullPath_)); 00413 00414 // Set smarty variables 00415 $fileDetail = Array( 00416 'name' => $name, 00417 'dirLink' => $dirLink, 00418 'size' => FbScanDir::sizeToString($size), 00419 'date' => FbScanDir::mtimeDateToString($mtime), 00420 'time' => FbScanDir::mtimeTimeToString($mtime)); 00421 $this->smarty_->assign('fileDetail', $fileDetail); 00422 00423 // Guess file type 00424 $ft = new FbFileType($this->fullPath_); 00425 if ($ft->canHighLight()) 00426 // Show source code using GeSHI 00427 $this->smarty_->assign('fileSourceCode', $ft->highlight()); 00428 else if ($ft->isTextFile()) 00429 // Show file's text content 00430 $this->smarty_->assign('fileTextContent', $ft->getText()); 00431 else if ($ft->isImage()) 00432 $this->smarty_->assign('isImage', true); 00433 00434 // Show detected charset 00435 if ($ft->isTextFile()) { 00436 $charset = $ft->getDetectedCharset(); 00437 if (false!=$charset) 00438 $this->smarty_->assign('detectedCharset', $charset); 00439 } 00440 00441 // Render smarty template 00442 $this->smarty_->display('FileBrowser.tpl'); 00443 } 00444 00448 private function actionDownload() { 00449 if ($this->isDir()) 00450 return $this->downloadDir(); 00451 00452 header('Content-Description: File Transfer'); 00453 header('Content-Type: application/force-download'); 00454 header('Content-Disposition: attachment; filename="'.basename($this->fullPath_).'"'); 00455 header('Content-Length: ' . filesize($this->fullPath_)); 00456 readfile ($this->fullPath_); 00457 exit(); 00458 } 00459 00463 private function actionDownloadUTF8() { 00464 $ft = new FbFileType($this->fullPath_); 00465 if (!$ft->isTextFile()) 00466 throw new ExceptionNotFound($this->fullPath_); 00467 00468 header('Content-Description: File Transfer'); 00469 header('Content-Type: application/force-download'); 00470 header('Content-Disposition: attachment; filename="'.basename($this->fullPath_).'"'); 00471 00472 $text = $ft->getText(); 00473 header('Content-Length: ' . strlen($ttext)); 00474 echo $text; 00475 exit(); 00476 } 00477 00481 private function downloadDir() { 00482 // Create temporary file 00483 $config = Config::instance(); 00484 $tmpDir = $config->documentRoot().$config->tmpDir(); 00485 $tmpFile = tempnam($tmpDir, 'FB_ZIP_'); 00486 00487 // Create and initialize ZIP archive 00488 $zip = new ZipArchive; 00489 $zip->open($tmpFile, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE); 00490 $fullPath = $this->fullPath_.'/'; 00491 $clientName = basename(ereg_replace('\/$', '', $this->fullPath_)); 00492 $zip->addEmptyDir($clientName); 00493 00494 // Walk directory recursively and add files/dirs to archive 00495 $dirStack = Array(''); 00496 while (0!=count($dirStack)) { 00497 $currentDir = array_pop($dirStack); 00498 $scanList = scandir($fullPath.$currentDir, 1); 00499 array_pop($scanList); 00500 array_pop($scanList); 00501 foreach ($scanList as $currentFile) { 00502 if (is_dir($fullPath.$currentDir.$currentFile)) { 00503 // directory 00504 $zip->addEmptyDir($clientName.'/'.$currentDir.$currentFile); 00505 array_push($dirStack, $currentDir.$currentFile.'/'); 00506 } 00507 else 00508 //file 00509 $zip->addFile($fullPath.$currentDir.$currentFile, $clientName.'/'.$currentDir.$currentFile); 00510 } 00511 } 00512 $zip->close(); 00513 00514 // Send archive to client 00515 header('Content-Description: File Transfer'); 00516 header('Content-Type: application/zip'); 00517 header('Content-Disposition: attachment; filename="'.$clientName.'.zip"'); 00518 header('Content-Length: ' . filesize($tmpFile)); 00519 readfile ($tmpFile); 00520 00521 // Clean up and exit 00522 unlink($tmpFile); 00523 exit(); 00524 } 00525 00530 private function actionThumbnail($maxSize) { 00531 $fileName = $this->fullPath_; 00532 $imgName = basename($fileName); 00533 $sizeStruct = @getimagesize($fileName); 00534 00535 // Guess image type 00536 $img = null; 00537 if (eregi('\.png$', $imgName)) 00538 $img = @imagecreatefrompng($fileName); 00539 else if (eregi('\.jpe?g$', $imgName)) 00540 $img = @imagecreatefromjpeg($fileName); 00541 else if (eregi('\.gif', $imgName)) 00542 $img = @imagecreatefromgif($fileName); 00543 if (!isset($img)) 00544 throw new ExceptionNotFound($fileName); 00545 00546 // Check size 00547 $max = max($sizeStruct[0], $sizeStruct[1]); 00548 $ratio = 1; 00549 if ($max > $maxSize) { 00550 $ratio = (float)$maxSize/$max; 00551 $width = $ratio * $sizeStruct[0]; 00552 $height = $ratio * $sizeStruct[1]; 00553 00554 // Create image with desired dimensions 00555 $imgDest = imagecreatetruecolor($width, $height); 00556 00557 // Smooth scale of image 00558 imagecopyresampled( 00559 $imgDest, $img, 0, 0, 0, 0, 00560 $width, $height, $sizeStruct[0], $sizeStruct[1]); 00561 imagedestroy($img); 00562 00563 // PNG thumbnail was too big, moved to jpeg 00564 /*header('Content-type: image/png'); 00565 imagepng($imgDest, null, 9);*/ 00566 00567 header('Content-type: image/jpeg'); 00568 imageinterlace($imgDest, 1); 00569 imagejpeg($imgDest, null, 90); 00570 imagedestroy($imgDest); 00571 } else { 00572 @imagedestroy($img); 00573 $this->actionFullImage(); 00574 } 00575 00576 exit(0); 00577 } 00578 00583 private function actionFullImage() { 00584 $fileName = $this->fullPath_; 00585 $imgName = basename($fileName); 00586 00587 // Guess image type 00588 $img = null; 00589 if (eregi('\.png$', $imgName)) 00590 header('Content-type: image/png'); 00591 else if (eregi('\.jpe?g$', $imgName)) 00592 header('Content-type: image/jpeg'); 00593 else if (eregi('\.gif$', $imgName)) 00594 header('Content-type: image/gif'); 00595 else if (ereg('\.svg$', $imgName)) 00596 header('Content-type: image/svg+xml'); 00597 else 00598 throw new ExceptionNotFound($fileName); 00599 00600 header('Content-length: '.filesize($fileName)); 00601 readfile($fileName); 00602 exit(); 00603 } 00604 00608 private function actionSendPdf() { 00609 $fileName = $this->fullPath_; 00610 if (!eregi('\.pdf$', $fileName)) 00611 throw new ExceptionNotFound($fileName); 00612 header('Content-type: application/pdf'); 00613 header('Content-length: '.filesize($fileName)); 00614 readfile($fileName); 00615 exit(); 00616 } 00617 00621 private function actionDirectAccess() { 00622 $fileName = $this->fullPath(); 00623 if (is_dir($fileName)) 00624 throw new ExceptionNotFound($fileName); 00625 // TODO: Send MIME 00626 if (ereg('\.css$', $fileName)) 00627 // Needed for FF! 00628 header('Content-type: text/css'); 00629 header('Content-length: '.filesize($fileName)); 00630 readfile($fileName); 00631 exit(); 00632 } 00633 00637 private function actionExecPhp() { 00638 $fileName = $this->fullPath(); 00639 if (is_dir($fileName)) 00640 throw new ExceptionNotFound($fileName); 00641 00642 if (ereg('\.php$', $fileName)) { 00643 // Script execution 00644 chdir(dirname($fileName)); 00645 require($fileName); 00646 } else { 00647 // Send non-script file direct 00648 $this->actionDirectAccess(); 00649 } 00650 exit(); 00651 } 00652 00656 private function asyncDispatch() { 00657 if (!$this->isDir()) 00658 // Unexpected navigation to file?? 00659 throw new ExceptionNotFound; 00660 00661 // Build file list 00662 $this->initSmarty(); 00663 $this->buildFileList(false); 00664 00665 // Content-type respose header is needed for Konqueror and FF 00666 // if using XMLHttp::resposeXML property, Opera does not need this 00667 Header('Content-type: application/xhtml+xml'); 00668 $this->smarty_->display('FileBrowserDispatch.tpl'); 00669 exit(); 00670 } 00671 00676 private function genHead() { 00677 $config = Config::instance(); 00678 $webRoot = $config->webRoot(); 00679 $fbScript = $webRoot.$config->scriptDir().'/FileBrowser.class.js'; 00680 $imgRoot = $webRoot.$config->imgDir(); 00681 echo ' <script type="text/javascript" src="'.$fbScript.'"></script>'."\n"; 00682 ?> <script type="text/javascript"> 00683 function fbOnload() { 00684 (new Image).src='<?php echo $imgRoot.'/alfa_pixel.gif' ?>'; 00685 fb = new FileBrowser; 00686 fb.imgExpanded = '<?php echo $imgRoot.'/tree_minus.png' ?>'; 00687 fb.imgCollapsed = '<?php echo $imgRoot.'/tree_plus.png' ?>'; 00688 fb.imgNoChildern = '<?php echo $imgRoot.'/tree_item.png' ?>'; 00689 fb.imgAlfaPixel = '<?php echo $imgRoot.'/alfa_pixel.gif' ?>'; 00690 fb.init(); 00691 } 00692 </script> 00693 <?php 00694 JSOnloadList::instance()->addFunction('fbOnload'); 00695 } 00696 }; 00697 ?>
1.5.4