Page 1 of 1

PHP File Browser Rate Topic: -----

#1 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 01 July 2011 - 12:34 PM

Hi there,
In this tutorial we're going to create our own PHP based file browser, so here is what we want to accomplish at the end of this tutorial:
  • We want to be able to browse a specific directory on our domain,obviously.
  • All of the directory contents must be displayed to the user (files and folders).
  • The ability to browse a child directory.
  • User must not go beyond a base directory or the allowed directory depth we specify.
  • File type filter can be used to filter the contents.
  • We should be able to get selected item path, type, name and a reference to the element itself.


What do we need?
PHP 5+ is required in order to get benefit of the scandir function (it can be done in PHP4+, but you need to use opendir and readdir).
JSON support which is PHP 5.2.0+ (or you can build the JSON string yourself).

Here we assumed PHP5.2.0+ is supported.
Also, I'll be using my own ajax snippet, but you can use yours as long as you know where to modify.
the Ajax will return the result in JSON format, you can modify this too if you update corresponding codes.

Note:
If you don't want to read about how it's made, you can jump directly to the How to use it section.

Preparing:
we will work with 3 files:
  • index.htm, holds the browser sections.
  • broswe.js, contains all needed javascript codes.
  • search_dir.php, the server-side script that reads requested directory and return it's contents.

styles.css, ajax.js are included in the attached file.
in addition to above files, we'll create a new folder (images) and add some .png icons to it to represent different file types, I'm using here the famfamfam's Silk pack which can be found here http://www.famfamfam...lab/icons/silk/

And this is how it looks:
Attached Image

index.htm:
Link the style.css file.
Load ajax.js file.
Load browser.js file(we'll work with it later).

let's add the following html elements:
  • div, as a container for the file browser.
  • text field (txtFilter) to pass wanted file types extensions, I'll put it inside the container, but this is up to you and it's optional.
  • button (btnrefresh), to refresh the contents.
  • paragraph (pPathDisplay), to display current folder path.
  • div (dvContents), a container for the retrieved folder contents.

<!DOCTYPE html>
<html>
<head>
<title>File Browser</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript" src="browser.js"></script>
</head>
<body>
<div class="browser">
	<p class="pfilter">File types filter
		<input type="text" id="txtFilter" value=""/>
		<input type="button" value="Refresh" id="btnrefresh"/>
	</p>
	<p id="pPathDisplay" class="pPathDisplay">Loading...</p>
	<div id="dvContents" class="dvContents">&nbsp;</div>
</div>
</body>
</html>



styles.css:
All what you need to know is that each row in the content list will have three class attached:
item, (even/odd) and ft_*, where * is the file extension of "folder"

search_dir.php:
Create a function (searchDir) that accepts 4 arguments and assign them default values:
base_dir: string, holds the base directory which is the root directory for users, and they are not allowed to navigate to an upper level directory.
p: string, the folder path we want to browse.
f: string, a comma separated file types to filter the result (user folder if you want to show folders).
allowed_depth: number, the maximum depth level of child directories the user can access.

Declare an empty array (contents) to hold files and folders found as a result of the function call.
Trim all textual arguments.
Set default base_dir to the current if passed as an empty string.
Remove all ./ and ../ sequence from requested folder path which may allow users to navigate to levels upper than base_dir's , and this is what you don't want.
Combine passed folder path with the (base_dir) so user doesn't need to know what is the base directory and use only relative paths.

Check if the new path is a directory and get the directory from the path if it's not.
Make sure the path ends with a slash (/) for later use.

Check if the directory depth is within allowed depth otherwise slice allowed path.
Explode passed filter (f) to an array (filter) of file types (extensions).

Scan the directory for files and return empty contents and return the requested path if failed for some reasons.
<?php
function searchDir($base_dir="./",$p="",$f="",$allowed_depth=-1){
	$contents=array();

	$base_dir=trim($base_dir);
	$p=trim($p);
	$f=trim($f);

	if($base_dir=="")$base_dir="./";
	if(substr($base_dir,-1)!="/")$base_dir.="/";
	$p=str_replace(array("../","./"),"",trim($p,"./"));
	$p=$base_dir.$p;
	
	if(!is_dir($p))$p=dirname($p);
	if(substr($p,-1)!="/")$p.="/";

	if($allowed_depth>-1){
		$allowed_depth=count(explode("/",$base_dir))+ $allowed_depth-1;
		$p=implode("/",array_slice(explode("/",$p),0,$allowed_depth));
		if(substr($p,-1)!="/")$p.="/";
	}

	$filter=($f=="")?array():explode(",",strtolower($f));

	$files=@scandir($p);
	if(!$files)return array("contents"=>array(),"currentPath"=>$p);



now it's time to process the scan result array (files) using (for) loop.
We get the path to the file by combining the path to the directory with the file name from the (files) array.

The file path may represent a folder too, so we check for this case, cause we'll treat folders differently.

Declare a new Boolean variable (add), this variable we'll be set to true when the file type matches the filter (in some other cases too).
Declare a file type variable (fType).

If the file path represents a file : (not directory)
Take the file extension and apply it to the filter.
Set the variable (add) to true if there is a match.

If the file path represents a directory:
Ignore when file name is (.) which represents the current directory.
Set the variable (add) to true, so all folder we'll be displayed.
Don't (add) the folder if there is a filter and this filter doesn't contain the folder file type (folder).

Get the parent directory path if the file name represents the parent directory (..).
We shouldn't display a link to the parent directory if it refers to directories beyond the base_dir (its path length is less than the base_dir path length), we must stick to the base_dir directory.

Remove the base_dir from file paths and added to the (contents) array if it passes the filter (add==true).
	for ($i=0;$i<count($files);$i++){
		$fName=$files[$i];
		$fPath=$p.$fName;

		$isDir=is_dir($fPath);
		$add=false;
		$fType="folder";
		
		if (!$isDir){
			$ft=strtolower(substr($files[$i],strrpos($files[$i],".")+1));
			$fType=$ft;	
			if ($f!=""){
				if (in_array($ft,$filter))$add=true;
			}else{
				$add=true;
			}
		}else{
			if($fName==".")continue;
			$add=true;
			
			if ($f!=""){
				if (!in_array($fType,$filter))$add=false;
			}

			if($fName==".."){
				if($p==$base_dir){
					$add=false;
				}else $add=true;
				
				$tempar=explode("/",$fPath);
				array_splice($tempar,-2);
				$fPath=implode("/",$tempar);
				if(strlen($fPath)<= strlen($base_dir))$fPath="";
			}
		}

		if($fPath!="")$fPath=substr($fPath,strlen($base_dir));
		if($add)$contents[]=array("fPath"=>$fPath,"fName"=>$fName,"fType"=>$fType);
	}




finally, we return the contents array and current path after we remove the base_dir from it.
	$p=(strlen($p)<= strlen($base_dir))?$p="":substr($p,strlen($base_dir));
	return array("contents"=>$contents,"currentPath"=>$p);
}



The last step is to call this function:
we get the path and filter parameters sent from the client using post method and call (searchDir) function.
and here I used the current directory (./) where the php file is placed for base_dir and set allowed_depth to (-1), but you can change them as needed.
$p=isset($_POST["path"])?$_POST["path"]:"";
$f=isset($_POST["filter"])?$_POST["filter"]:"";
echo json_encode(searchDir("./",$p,$f,-1));



browser.js:
This file contains one main function which accepts one argument(params as Object) and 3 child functions for internal use.
params object can holds the following properties:
contentsDisplay: html element reference, where the folder contents will be listed.
currentPath: String, represents the base_dir related path (you type the path as you are in the base_dir set in the php code).
filter: String/html element reference, that hold comma separated file types (extensions) to be displayed.
loadingMessage: String, the message to be displayed in the pathDisplay while loading folder contents.
pathDisplay: html element reference, this is where the current folder path will appear.
pathMaxDisplay: Number, the maximum characters to be shown in the pathDisplay element.
refreshButton: html element reference, the refresh button.
openFolderonselect: Boolean, open selected folder once selected.
onselect: Function, fired on selecting an item and accepts one object argument which has 4 properties: (type:file type,path:file path,title:file name, item:clicked html element)
data: string, any extra information you may want to pass to the php file (maybe a file browser identity so the base_dir can change accordingly)

Note:
All of these properties are optional, but at least add the contentsDisplay otherwise the result will be included directly into the document's body.

function browser(params){
	if(params==null)params={};
	if(params.contentsDisplay==null)params.contentsDisplay=document.body;
	if(params.currentPath==null)params.currentPath="";
	if(params.filter==null)params.filter="";
	if(params.loadingMessage==null)params.loadingMessage="Loading...";
	if(params.data==null)params.data="";



search function:
Show loading message if possible.
Get the filter.
Send a request to the search_dir.php file and receive the result in the showFiles function
Add click event to the refresh button if assigned.
	var search=function(){
		if(params.pathDisplay!=null)params.pathDisplay.innerHTML=params.loadingMessage;
		
		var f=typeof(params.filter)=="object"?params.filter.value:params.filter;
		var a=new Ajax();
		with (a){
			Method="POST";
			URL="search_dir.php";
			Data="path="+params.currentPath+"&filter="+f+"&data="+params.data;
			ResponseFormat="json";
			ResponseHandler=showFiles;
			Send();
		}
	}
	
	if(params.refreshButton!=null)params.refreshButton.onclick=search;



showFiles function:
Get the current folder path received from server.
Remove unnecessary (./ , ../ , .) from the beginning of the path.
subtract the last pathMaxDisplay if applicable.
Display the path in the pathDisplay.
Clear the contentsDisplay contents.
Declare the oddeven variable to hold the row's odd/even class.
Loop the contents.
Create a paragraph for each element (el) in the contents.
Set the title, fPath, fType attributes.
Set the class name depending on the file type and the odd/even status.
Display the file name in the (el) innerHTML.
Append the paragraph (el) to the (contentsDisplay).
Swap the oddeven variable.
Add click event to the (el) to call the selectItem function
	var showFiles=function(res){
		if(params.pathDisplay!=null){
			var p=res.currentPath;
			p=p.replace(/^(\.\.\/|\.\/|\.)*/g,"");
			
			if(params.pathDisplay!=null){
				params.pathDisplay.title=p;
				if(params.pathMaxDisplay!=null){
					if(p.length>params.pathMaxDisplay)p="..."+p.substr(p.length-params.pathMaxDisplay,params.pathMaxDisplay);
				}
				params.pathDisplay.innerHTML="[Rt:\] "+p;
			}
		}
		
		params.contentsDisplay.innerHTML="";
		var oddeven="odd";

		for (i=0;i<res.contents.length;i++){
			var f=res.contents[i];
			var el=document.createElement("p");
			with(el){
				setAttribute("title",f.fName);
				setAttribute("fPath",f.fPath);
				setAttribute("fType",f.fType);
				className=oddeven + " item ft_"+f.fType;
				innerHTML=f.fName;
			}
			params.contentsDisplay.appendChild(el);
			oddeven=(oddeven=="odd")?"even":"odd";
			el.onclick=selectItem;
		}
	}



selectItem function:
This function will be fired whenever the user clicks an item.
Get the ftype, fpath and title attribute we previously assigned to each element.
Call onselect function if assigned and pass it two arguments: an object has these properties: file type, file path, file title and the html element itself, and the params object.
If selected item is a folder and openSelectedFolder property is set to true then set the browser current path to this folder and open it by calling (search) function.
	var selectItem=function(){
		var ftype=this.getAttribute("fType");
		var fpath=this.getAttribute("fPath");
		var ftitle=this.getAttribute("title");

		if(params.onselect!=null)params.openFolderonselect=params.onselect({"type":ftype,"path":fpath,"title":ftitle,"item":this},params);
		if(params.openFolderonselect==null)params.openFolderonselect=true;

		if(ftype=="folder" && params.openFolderonselect){
			params.currentPath=fpath;
			search();
		}
	}



and finally call the search function, so users don't need to manually refresh after calling browser function.
	search();
}



How to use it ?
Back to index.php file, let's add a script tag right after we load the browser.js file.
We'll add a function (init) to be called on document's load event.
<script type="text/javascript">
function init(){

}
</script>


this function will call the previously created (browser) function .
I'll execute it on the document's load event so we make sure all needed codes and elements are loaded and ready to use;
so update the document's body tag like this:
<body onload="init()">


now, put the following code inside (init) function:
this function call send an object that specify the contents display area, refresh button, path display area, element holds the file types filter and sets the current path to images folder which must be a child directory of the base_dir specified inside the php file.
browser({
	contentsDisplay:document.getElementById("dvContents"),
	refreshButton:document.getElementById("btnrefresh"),
	pathDisplay:document.getElementById("pPathDisplay"),
	filter:document.getElementById("txtFilter"),
	currentPath:"images"
	});



Notes:
You can set the openFolderonselect params property to false if you don't want at all to open selected folder.
openFolderonselect:true


Or you can created onselect handler to process selected item before you decide what to do with this item.
if this function returns false and it was a folder, selected folder will not be opened, but it will if the function returns true.
this function overwrites the openFolderonselect property when not returning null.

onselect:function(item,params){
		if(item.type=="folder")
			return confirm("Do you want to open this Folder : "+item.path);
		else
			alert("You selected :"+item.path)
	}


make sure to add a comma after each property declaration to params argument except for the last one.
since we pass params object to the onselect function, it's possible for you to add a custom properties to it when calling the browser function to be used later from within onselect function using its params argument.


all files attached here Attached File  browser.zip (16.22K)
Number of downloads: 4634

you can also try it here, I set the currentPath to images and added a testing junk folder it :)


Note:
Posted first time as an answer here http://www.dreaminco...ost__p__1357461
but it's better now, at least this what I think :)


Hope it helps

Is This A Good Question/Topic? 3
  • +

Replies To: PHP File Browser

#2 CTphpnwb  Icon User is online

  • D.I.C Lover
  • member icon

Reputation: 2960
  • View blog
  • Posts: 10,186
  • Joined: 08-August 08

Posted 16 July 2011 - 04:07 PM

I haven't tested this myself, but I wanted to say to any newbies checking this tutorial out that you should pay attention to the way things are organized in this tutorial. HTML, PHP, CSS, and Javascript are all distinct snippets of code in their own files and with proper indenting. By organizing the code, ahmad_511 has made sure that it is readable, and so even if it didn't work you can see that editing/debugging wouldn't be hard.
Was This Post Helpful? 1
  • +
  • -

#3 Dormilich  Icon User is offline

  • 痛覚残留
  • member icon

Reputation: 3530
  • View blog
  • Posts: 10,179
  • Joined: 08-June 10

Posted 22 July 2011 - 08:18 AM

I know it may be a bit mean, but that directory iterating can be simplified by a great deal:
//# $path could be something as "path/to/*.$extension" to make it filter for extensions
//# or just "path/to/*$searchTerm*"
$git = new GlobIterator($path, FilesystemIterator::NEW_CURRENT_AND_KEY|FilesystemIterator::SKIP_DOTS);
foreach ($git as $file)
{
    //# just a simple demo output
    echo $file->getBasename(), "<br>";
}

Was This Post Helpful? 3
  • +
  • -

#4 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 22 July 2011 - 03:17 PM

@Dormilich,
to be honest, I wasn't aware of The Filesystem iterator, so it's something for me to consider next time (this adds just another item to the list of things I like about DIC.)

according to the php help file, the FilesystemIterator is php 5.3+ and I can see a lot of web hosts still using php 5.2.something (or at least ones I'm dealing with), in my attempt I was trying to keep it php 5.2+ compatible.

also, using *.$extension is case-sensitive so using it to filter file types may ends to the same steps I'm currently using,But it certainly helps with file names.
another thing is that we can filter multiple file types at once in the way I did by using a comma separated extensions (unless I'm missing something about the FilesystemIterator).



Thanks for your input :)
Was This Post Helpful? 0
  • +
  • -

#5 Dormilich  Icon User is offline

  • 痛覚残留
  • member icon

Reputation: 3530
  • View blog
  • Posts: 10,179
  • Joined: 08-June 10

Posted 22 July 2011 - 05:04 PM

View Postahmad_511, on 23 July 2011 - 12:17 AM, said:

also, using *.$extension is case-sensitive so using it to filter file types may ends to the same steps I'm currently using,But it certainly helps with file names.
another thing is that we can filter multiple file types at once in the way I did by using a comma separated extensions (unless I'm missing something about the FilesystemIterator).

nothing prevents you of using other iterators on iterators. most of the other SPL iterators actually need iterators to be passed (like the RecursiveIteratorIterator, FilterIterator, NoRewindIterator, Ö)

e.g.
//# out of one of my projects
$dirit = new RecursiveDirectoryIterator($dir, FilesystemIterator::NEW_CURRENT_AND_KEY|FilesystemIterator::SKIP_DOTS);
$reit  = new RegexIterator($dirit, '#- \d+\.(' . $valid_ext . ')$#');


that ISPs donít use PHP 5.3 doesnít mean they donít have it. for instance, my ISP offers it, but you have to explicitly request it to be activated.
Was This Post Helpful? 2
  • +
  • -

#6 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 22 July 2011 - 05:23 PM

that's good enough, the RegexIterator is just great

and thanks for the tip :)

Regards
Was This Post Helpful? 0
  • +
  • -

#7 codeprada  Icon User is offline

  • Changed Man With Different Priorities
  • member icon

Reputation: 946
  • View blog
  • Posts: 2,355
  • Joined: 15-February 11

Posted 25 July 2011 - 05:29 AM

Dormilich said what I was going to say but nonetheless a good solid tutorial. One point to note is to set error handlers instead of suppressing errors with @.

Keep the tutorials coming.
Was This Post Helpful? 2
  • +
  • -

#8 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 25 July 2011 - 06:14 AM

Quote

One point to note is to set error handlers instead of suppressing errors with @.

I'll keep that in mind :)

Thanks
Was This Post Helpful? 0
  • +
  • -

#9 tazosmr  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 21-June 13

Posted 21 June 2013 - 08:06 AM

Update! add these line too !
1)to access the upper directories, you need to open search_dir.php and change the last line's
searchDir
function (where is
echo json_encode(searchDir("../",$p,$f,-1));

2) a person needs to click "REFRESH" button to sort out the files. it's better to edit "index.htm" file, and instead of
<input type="text" id="txtFilter" value="" />

enter this:
<input type="text" id="txtFilter" value="" onkeydown="if (event.keyCode == 13) document.getElementById('btnrefresh').click()" />

Was This Post Helpful? 0
  • +
  • -

#10 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 21 June 2013 - 02:06 PM

Hello tazosmr, and thanks for the updates

Quote

1)to access the upper directories, you need to open search_dir.php and change the last line's


well, as I mentioned in the tutorial the searchDir function accept the base_dir argument to limit the user from accessing an upper level

Quote

base_dir: string, holds the base directory which is the root directory for users, and they are not allowed to navigate to an upper level directory.

but in javascript I set the currentPath to "images" folder, so the browser starts from the images folder at first launch and allows you to go one level up to reach the base directory you pass to the searchDir function.

what I want to say is: it's not a bug, it's intended to do that and you're free to choose the base dir,
anyways, it's better to define a BASE_DIR constant fro example and use it in the search_dir function as a paramaeter


Quote

2) a person needs to click "REFRESH" button to sort out the files. it's better to edit "index.htm" file, and instead of

"event" might not be supported by some browsers

check this Introduction to Events

Regards
Was This Post Helpful? 0
  • +
  • -

#11 matheusxaviersi  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 26-June 13

Posted 26 June 2013 - 10:15 PM

I have done an expansion on files supported by the system I changed the css file and added new things now supports audio files lua source files cab files bat files sql files all of those have icons now tweaked some icons to bring a more intuitive interface.
I don't touched the system itself but the whole css yes.

Attached File(s)


Was This Post Helpful? 0
  • +
  • -

#12 Danielx64  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 16-March 14

Posted 16 March 2014 - 07:21 PM

Hi there,

I have a question, I'm currently using jquery.filetree (in a messy and bad way) and I would like to replace it something that is lighter. Is there any chance that this script can be changed so that when someone selects a file called config.php it will enter the file path (without the config.php) in a textbox below?

Thank-you :)
Was This Post Helpful? 0
  • +
  • -

#13 ahmad_511  Icon User is offline

  • MSX
  • member icon

Reputation: 131
  • View blog
  • Posts: 722
  • Joined: 28-April 07

Posted 16 March 2014 - 09:16 PM

Hi Daniel,
you can get what you want by reading the params.currentPath which is passed to the onselect callback function as the second argument

something like this:

onselect:function(item, params){
	if(item.type=="folder"){
		// Read the path from item.path property if selected item is a folder
		document.getElementById('ID_OF_PATH_ELEMENT').value=item.path;
	}else{
		// Otherwise read it from params.currentPath
		document.getElementById('ID_OF_PATH_ELEMENT').value=params.currentPath;
	}
}



I hope it helps

Edit:
I'm not sure why the letter S appears small in onselect in the code above

This post has been edited by ahmad_511: 16 March 2014 - 09:22 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1