I'm currently working on a web project for a gaming group, and I'm trying to test myself and expand my general knowledge and toolbox. One of the features I'm working on requires me to "clean" the URL of a requested file. I have the system in place, and it works beautifully. Now, in order for my website to work 'flawlessly', I need to deny access to certain folders, and all files within, through htaccess. But there's a slight catch... It needs to operate without the user even realising.
My file structure, preferred access method, path, and status, are as follows:
rogue {allow} {direct} {%domain%/dashboard/} {200}
css {deny} {direct} {%domain%/css/} {403}
js {deny} {direct} {%domain%/js/} {403}
fonts {deny} {direct} {%domain%/fonts/} {403}
files {deny} {direct} {%domain%/files/} {403}
image {deny} {direct} {%domain%/image/} {403}
includes {deny} {direct} {%domain%/includes/} {403}
php {deny} {direct} {%domain%/php/} {403}
index.php {allow} {direct} {%domain%/dashboard/} {200}
html_footer.php {deny} {direct} {%domain%/dashboard/} {404}
html_header.php {deny} {direct} {%domain%/dashboard/} {404}
.htaccess {deny} {direct} {%domain%/dashboard/} {403}
The above list is actually an example of how I'd like the website to operate... The index.php file controls the requested 'files' and displays them accordingly. For example, %domain%/get-in-touch/ would display the contact.php file within the %domain%/includes/ directory. Calling index.php directly displays the same as %domain%/dashboard/.
The following code is from my .htaccess file.
Please, ignore the # NN snippets. These are for your benefit to reduce the need for me to add code snippets. I suppose it would help you, too... You could directly reference the line numbers should you need to.
DirectoryIndex index.php # 01
Options All +FollowSymlinks -Indexes # 02
# 03
<IfModule mod_rewrite.c> # 04
RewriteEngine On # 05
#RewriteBase /rogue/ # 06
# 07
RewriteCond %{REQUEST_FILENAME} !-d # 08
RewriteCond %{REQUEST_FILENAME} !-f # 09
RewriteRule ^(.*?)$ index.php [QSA,L] # 10
# 11
RewriteCond %{REQUEST_FILENAME} -d # 12
RewriteRule ^(.*?)(js|image|css|includes|php|files)(.*)$ index.php [QSA,L] # 13
</IfModule> # 14
I commented out line 06 as the system didn't seem to need it, but I kept it for my own reference and in the off chance that I may need it later. Lines 08 through 10 control redirection to index.php when a file or a directory doesn't exist, and then the responding file controls the rest. The folders css/js/fonts/image/includes/php all exist, so lines 12 and 13 prevent access to them and redirect to index.php where the array of 'directories' can be located and the relevant files can be selected for inclusion. An example of the array is included at the bottom of the post. Lines 12 and 13 can be moved above line 08 and there would be no change, but removing them from the file causes a default 403 Forbidden error to fire.
Removing lines 08, 09, 12 and 13 causes the page to break by loading the files located in the image, files and fonts directories as MIME type text/html, however, the files within any other directories are seemingly unaffected.
Lines 12 and 13 perform as required, until you request access to a file within those directories. The file is displayed, and all of my secrecy is exposed to the world. As a result, certain files that have a great deal of importance are shown to the users. I want to prevent anybody from accessing the files within any folder I choose to deny access to, and have it redirect them to index.php and show a 403. I've attempted it by adding RewriteCond %{REQUEST_FILENAME} -f between lines 12 and 13, but the page shows as broken and fails to load resources.
NOTE: I had to remove -Indexes from line 02 as file requests from JavaScript files would be denied, doing this has also prevented lines 12 and 13 from doing as they were intended, regardless of their placement.
Directory Array [PHP]
$_INCDIRECTORY = 'includes/';
$_PATHINCLUDES = array(
'dashboard' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'dashboard.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'news' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'news.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'search' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'search.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
// Excluded/Denied directories;
'css' => array(
'allow' => false,
'path' => 'css/',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
// Error pages;
'403' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'errors/403.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'404' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'errors/404.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
);
After spending a while searching for a solution, and the usual trial and error attempts at solving it, I have come up with a solution that works exactly how I want it to. It seemed more fitting to answer my own question for reference to anybody who should come across the same issue.
The following code rests within the .htaccess file at the root directory of your website. A description of what each line does follows after the snippet.
The .htaccess Trickery
DirectoryIndex index.php
Options +FollowSymlinks -Indexes
<IfModule mod_rewrite.c>
# Enable mod_rewrite;
RewriteEngine On
# Detect if SSL Module is enabled;
<IfModule mod_ssl.c>
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,NC,L]
</IfModule>
# Check if the directory exists;
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{HTTP_REFERER} !^((((https|http)://([w]{3}\.)?)([a-z0-9\.]+)/).*)$ [NC]
RewriteRule ^(.*?)(js|image|css|includes|php|files)(.*)$ index.php [L]
# Check if the file exists;
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{HTTP_REFERER} !^((((https|http)://([w]{3}\.)?)([a-z0-9\.]+)/).*)$ [NC]
RewriteRule ^(.*?)(js|image|css|includes|php|files)(.*)$ index.php [L]
# Redirect to 'index.php', regardless of the request;
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*?)$ index.php [QSA,L]
</IfModule>
DirectoryIndex index.php defines the primary 'homepage' of the website to index.php, this is not a requirement but it may be good practice to use this should your web-host change the default settings for Apache to use a different default index.
Options +FollowSymlinks -Indexes does the following: +FollowSymlinks allows Apache to 'follow symbolic links', and -Indexes prevents any subfolders from being listed should an index.{ext} be missing. NOTE: Removing All from the previous configuration prevented script calls from being included within the exclusion, allowing all AJAX functionality.
<IfModule mod_rewrite.c> ... </IfModule> allows the basis of the htaccess file to function without the Rewrite Module being enabled within your Apache config.
RewriteEngine On enables the Rewrite Engine. Setting the value to Off disables this feature, and none of the underlying code will operate.
<IfModule mod_ssl.c> ... </IfModule> allows the contained code to function, providing the Apache installation includes, and uses, the SSL Module.
RewriteCond %{HTTPS} off then checks to see if %{HTTPS} is being used by the request, and then RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,NC,L] permanently redirects the user to an SSL enabled URI. %{HTTP_HOST}%{REQUEST_URI} simply allows the same functionality over multiple websites without the need of a reconfiguration.
RewriteCond %{REQUEST_FILENAME} -d checks to see if the requested directory exists, RewriteCond %{HTTP_REFERER} !^((((https|http)://([w]{3}\.)?)([a-z0-9\.]+)/).*)$ [NC] then checks to see if the referrer was NOT requesting a full URL, basically a domain name. The RegEx following RewriteCond checks to see if the request uses http or https, optionally uses www. and then checks the rest of the URL. Then RewriteRule ^(.*?)(js|image|css|includes|php|files)(.*)$ index.php [L] checks to see if any of the folders requested were included within the RegEx and then redirects to index.php where the PHP code will handle the tricky stuff. NOTE: See this post for reference.
Following the same course as the above code, RewriteCond %{REQUEST_FILENAME} -f does the same. But handles file requests, should they exist. NOTE: Again, see this post for reference.
RewriteCond %{REQUEST_FILENAME} !-d and RewriteCond %{REQUEST_FILENAME} !-f check to see whether the requested file were a directory or a file and whether they existed, or not, and then RewriteRule ^(.*?)$ index.php [QSA,L] redirected the entire request to the index.php where the request would be correctly handled by PHP. NOTE: To those who didn't already know this, the ! character can be used in most cases as a shorthand for not. NOTE: See this page for a mod_rewrite variables cheatsheet. And this tool for RegEx testing.
The PHP Magic
Following the post, found here, to create your PHP file and then use the .htaccess config from within this answer would seemingly work perfectly, and provide the majority of the functionality that you may request. Severe modifications to the index have been made in my case, the more important ones are displayed below.
switch( $path_info['call_parts'][0] ) {
case 'about-us':
include 'about.php';
break;
case 'users':
include 'users.php';
break;
case 'news':
include 'news.php';
break;
case 'products':
include 'products.php';
break;
default:
include 'front.php';
break;
}
The above code can be changed to create a cleaner environment and an easier to understand configuration. Since some of the website functionality can be controlled by the requested files, changing the code to a more suitable alternative would be advised.
For example: All of your requests can be added to an array that contains a few variables based on the request.
$_INCDIRECTORY = 'includes/';
$_PATHINCLUDES = array(
'dashboard' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'dashboard.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'news' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'news.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'search' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'search.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'register' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'register.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
####################################
## DENIED PAGES/DIRECTORIES ########
####################################
'css' => array(
'allow' => false,
'path' => 'css/',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'files' => array(
'allow' => false,
'path' => 'files/',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
####################################
## ERROR PAGES #####################
####################################
'403' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'errors/403.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
'404' => array(
'allow' => true,
'path' => $_INCDIRECTORY.'errors/404.php',
// The following must remain FALSE; This value will be changed 'on-the-fly';
'exists' => false,
),
);
The initial array contains a list of each file, or directory, of the request. Each sub-array then contains request-specific variables that control the inclusion script later on. 'allow' defines whether the user is allowed to view the request. true would display the page, and false would display a 403 error. 'path' defines the file that is requested by the script, the file is checked by another snippet to see if it actually exists before being displayed when everything is green. 'exists' is always false at the beginning of the request, the following changes that.
foreach( $_PATHINCLUDES as $key => &$value ) {
## Check to see if browsing the 'directory' is allowed
## before checking to see if the 'directory' exists;
if( $value['allow'] == true ) {
## Browsing is allowed, check if 'directory'
## actually exists and set 'exists' to '=1';
if( file_exists( $value['path'] ) == true ) {
## Set the value 'exists' to 'true';
$value['exists'] = true;
};
} else {
## Browsing the 'directory' is not allowed,
## so the response should be a '403';
};
};
unset( $value );
The above code handles the array and sets 'exists' to true or false accordingly. Providing the 'allow' variable is true. There is no point in checking a folder/file the user can't access when its existence doesn't matter. .. as $key => &$value .. allows the value of the array to be changed within the foreach( .. ) loop, unset( $value ); removes the variable from being accessed by anything after the loop. NOTE: See this post for reference.
To prevent any nasty PHP errors, such as requested offset [N] not set, etc... Use the following code. This checks that the variable actually exists before attempting to use it, if it doesn't exist, then it uses a predefined value.
$_CALL_PART = isset( $path_info['call_parts'][0] ) ? $path_info['call_parts'][0] : 'dashboard';
$_REQUESTED = isset( $_PATHINCLUDES[ $_CALL_PART ] ) ? $_PATHINCLUDES[ $_CALL_PART ] : $_PATHINCLUDES['404'];
The final code snippet controls the behavior of the included file.
## Check to see if access to the file is
## allowed before doing anything else;
if( $_REQUESTED['allow'] == true ) {
## The file can be browsed to, so check if it exists;
if( $_REQUESTED['exists'] == true ) {
## The file exists, so include it;
include( $_REQUESTED['path'] );
} else {
## The file does not exist, so display a 404 page;
if( $_PATHINCLUDES['404']['exists'] == true ) {
## Check to ensure the file exists before including it;
## This prevents an error;
include( $_PATHINCLUDES['404']['path'] );
} else {
## The file does not exist, so simply display a message;
echo '<h1>404 - File Not Found: No ErrorDocument supplied</h1>';
};
};
} else {
## The file cannot be browsed to, so display a 403;
if( $_PATHINCLUDES['403']['exists'] == true ) {
## Check to ensure the file exists before including it;
## This prevents an error;
include( $_PATHINCLUDES['403']['path'] );
} else {
## The file does not exist, so simply display a message;
echo '<h1>403 - Forbidden: No ErrorDocument supplied</h1>';
};
};
if( $_REQUESTED['allow'] == true ) { ... } else { ... } checks to see whether the request is allowed. If so, the next section of the script can be executed. if( $_REQUESTED['exists'] == true ) { ... } else { ... } checks whether the requested file exists. If so, the script executes include( $_REQUESTED['path'] ); which displays the page. Otherwise, the script then returns a 404 which is subsequently checked in the same manner, excluding the allow parameter, and then displays it. If the prior if( .. ) statement was to return true, then the 403 error would be checked and displayed if the exists parameter returned as true. If neither the 403 and the 404 pages could be found, then the script would show a basic error response.
Related
I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.
I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.
I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.
I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.
I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.