lib/FileBrowser.class.php

Go to the documentation of this file.
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 ?>

Generated on Sat Mar 8 10:26:43 2008 for Dudka.cz by  doxygen 1.5.4