Manage slugs with PHP and .htaccess (mod_rewrite) - php

Let's call my site:
www.example.com
and I have a PHP file like this:
www.example.com/product.php?id=50
I would like to access it by using
www.example.com/product/50
but ALSO, very important, I have several subdirectories like
www.example.com/subsite/product.php?id=50
www.example.com/subsubsite/product.php?id=50
That must become
www.example.com/subsite/product/50
www.example.com/subsubsite/product/50
How can I solve it at best with PHP and .htaccess using mod_rewrite?
I banged my head with other questions like this one but to no avail.
I can't seem to find a solution that works flawlessly, taking care of all imported files like CSS, JS and PHP classes.

Ok so this might not be the complete answer but should help you find your way.
You can use regex to match your desired path pattern. So for example your htaccess might look something like...
# Check if module is installed
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Check query for matching pattern and pass id, but also append additional query strings
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\/]+\/)?product\/([0-9]+)$ /$1product.php?id=$2 [L,QSA]
# If not file or directory on server, send to 404.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /404.php [L]
</IfModule>
And what this does is...
1. Match the uri with a regex pattern
Regex: ^([^\/]+\/)?product\/([0-9]+)$
^ - Start of string.
([^\/]+\/)? - matches any directory (if exists) and stores it for reuse.
product\/([0-9]+) - Your desired path e.g. product/50 and stores the number "id" for reuse.
$ - End of string.
2. Pass captured directory and id to our file
Like so: /$1product.php?id=$2 [L,QSA]
$1 is our directory name including the trailing slash e.g. subsubsite/
$2 is our product id e.g. 50
[L,QSA] The QSA flag means we can access additional query string parameters e.g. /product/50?show=all&updated=1. More about flags can be found here http://httpd.apache.org/docs/current/rewrite/flags.html#flag_qsa
3. 404 anything not matching
Like so:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /404.php [L]
!-f If request is not a file
!-d If request is not a directory
/404.php The file used for presenting a 404 error.
Getting the id...
With the above, you can get the ID within your product.php file like so:
$id = (int)$_GET[ 'id' ];

Related

htaccess rewrite a multi-query string into a path

How would i go about changing the query string
file.php?id=number&string=some-words
into this
file/number/some-words/
I know this has been asked a million times before, but I've looked at a number of solutions on here and they were all single query based (as in just ?something and not ?something&something-else).
Also once rewritten, does the php still read the original page query-string when using $_GET or $_REQUEST etc... even though it now appears as a path?
Any help is appreciated.
Thanks.
RewriteRule takes a regular expression which can be as complicated as you want it followed by the real URL that will load your file. You put parenthesis around the parts you want to capture in the regex and can reference those with $1 for first group, $2 for second group and so on in the URL part. For example:
RewriteRule ^(\w+)/(\d+)/(.*)$ index.php?file=$1&id=$2&words=$3
This would match 3 groups:
letters/numbers up to the first slash
some numbers up to the second slash
anything after that including additional slashes
And those can be referenced by $1, $2, $3 as seen in the second part with index.php.
The only issue with this though is that if you are missing any one part, the pattern in the rule won't match. So you either need to have a separate rule for each variant:
#matches all 3 parts
RewriteRule ^(\w+)/(\d+)/(.*)$ index.php?file=$1&id=$2&words=$3
#matches the first 2 parts
RewriteRule ^(\w+)/(\d+)$ index.php?file=$1&id=$2
#matches just the first part
RewriteRule ^(\w+)$ index.php?file=$1
#matches everything else
RewriteRule ^.*$ index.php
Or you can do what is usually called bootstrapping which is where you use a single RewriteRule to redirect everything to a single php file like so:
RewriteRule ^(.*)$ index.php
And then you can just use php to determine what the different parts are. Inside php there is a built in server variable $_SERVER['REQUEST_URI'] that will give you the URI part of the url which is everything after the domain and first slash including any query string parameters. This is based on the URL that the user requested and not the one rewritten by apache. You can explode('/', $_SERVER['REQUEST_URI']) to get the individual parts and do whatever you want with them.
You can place your code in Apache .htaccess files. This could look something like this:
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^users/(\d+)*$ ./profile.php?id=$1
RewriteRule ^threads/(\d+)*$ ./thread.php?id=$1
RewriteRule ^search/(.*)$ ./search.php?query=$1
Or you can use only htaccess and php:
htaccess
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^.*$ ./index.php
PHP
<?php
#remove the directory path we don't want
$request = str_replace("/envato/pretty/php/", "", $_SERVER['REQUEST_URI']);
#split the path by '/'
$params = split("/", $request);
?>
And it will still read the original page query-string.

Trouble moving path information to a query string in htaccess

I would like to rewrite www.mysite.com/a/b/slug to www.mysite.com/a/b/index.php?id=slug.
I am trying to capture the last segment of the path i.e. slug and use it in the query string -- similar to this example.
So I have the following lines in my .htaccess sitting in my public_html folder:
RewriteEngine on
RewriteRule ^a/b/([^/]+) a/b/index.php?id=$1 [L]
In my a/b/index.php, when I go to www.site.com/a/b/slug, $_GET['id'] returns index.php opposed to slug. I am not sure why.
How can I capture the last segment and use it in the query string?
You need to add rewrite conditions in your .htaccess - at the moment, it is attempting to rewrite /a/b/index.php as well!
Try adding RewriteCond %{REQUEST_FILENAME} !-f and RewriteCond %{REQUEST_FILENAME} !-d

Pretty URL via htaccess using any number of parameters

Actually i have this URL:
http://www.example.com/index.php?site=contact&param1=value1&param2=value2&param3=value3
But i want to have this URL format:
http://www.example.com/contact/param1:value1/param2:value2/param3:value3
So the "contact" goes to variable $_GET["site"] and rest of parameters should be able to access via $_GET["param1"], $_GET["param2"] etc. The problem is, it has to work with any number of parameters (there could be param4 or even param50 or any other name of parameter). Is it possible via htaccess to cover all these cases?
Mod_rewrite has a maximum of 10 variables it can send:
RewriteRule backreferences:
These are backreferences of the form $N (0 <= N <= 9), which provide access to the grouped parts (in parentheses) of the pattern, from the RewriteRule which is subject to the current set of RewriteCond conditions.
mod_rewrite manual
so what you desire is NOT possible with htaccess only. a common way is to rewrite everything to one file and let that file determine what to do in a way like:
.htaccess
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L,NC]
index.php
$aUrlArray = explode('/',str_ireplace(',','/',$_SERVER['REQUEST_URI'])); // explode every part of url
foreach($aUrlArray as $sUrlPart){
$aUrlPart = explode(':',$sUrlPart); //explode on :
if (count($aUrlPart) == 2){ //if not 2 records, then it's not param:value
echo '<br/>paramname:' .$aUrlPart[0];
echo '<br/>paramvalue' .$aUrlPArt[1];
} else {
echo '<br/>'.$sUrlPart;
}
}
Garytje's answer is almost correct.
Actually, you can achieve what you want with htaccess only, even if this is not something commonly used for that purpose.
Indeed, it would be more natural to delegate the logic to a script. But if you really want to do it with mod_rewrite, there are a lot of techniques to simulate the same behaviour. For instance, here is an example of workaround:
# Extract a pair "key:value" and append it to the query string
RewriteRule ^contact/([^:]+):([^/]+)/?(.*)$ /contact/$3?$1=$2 [L,QSA]
# We're done: rewrite to index.php
RewriteCond %{QUERY_STRING} !^$
RewriteRule ^contact/$ /index.php?site=contact [L,QSA]
From your initial example, /contact/param1:value1/param2:value2/param3:value3 will first be rewritten to /contact/param2:value2/param3:value3?param1=value1. Then, mod_rewrite will match it again and rewrite it to /contact/param3:value3?param1=value1&param2=value2. And so on, until no pair key:value is found after /contact/. Finally, it is rewritten to /index.php?site=contact&param1=value1&param2=value2&param3=value3.
This technique allows you to have a number of parameters greater than 9 without being limited by mod_rewrite. You can see it as a loop reading the url step by step. But, again, this is maybe not the best idea to use htaccess only for that purpose.
This is entirely doable using some creative htaccess and PHP. Effectively what you are doing here is telling Apache to direct all page requests to index.php if they are not for a real file or directory on the server...
## No directory listings
IndexIgnore *
## Can be commented out if causes errors, see notes above.
Options +FollowSymlinks
Options -Indexes
## Mod_rewrite in use.
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]
After this all you need to do is go into PHP and access the full user requested URL structure using the $_SERVER['REQUEST_URI'] superglobal and then break it down into an array using explode("/", $_SERVER['REQUEST_URI']).
I currently use this on a number of my sites with all of the sites being served by index.php but with url structures such as...
http://www.domain.com/forums/11824-some-topic-name/reply
which is then processed by the explode command to appear in an array as...
0=>"forums", 1=>"11824-some-topic-name",2=>"reply"
Try this..
.htaccesss
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* /index.php [L,QSA]
index.php
$uri = $_SERVER['REQUEST_URI'];
$uri_array = explode( "/", $uri );
switch ( $uri_array[0] ) {
case '':
/* serve index page */
break;
case 'contact':
// Code
break;
}
This is doable using only htaccess with something along the lines of...
([a-zA-Z0-9]+):{1}([a-zA-Z0-9]+)
([a-zA-Z0-9]+) will match alpha-numeric strings.
:{1} will match 1 colon.
Expanding from there will probably be required based on weird URLs that turn up.

mod_rewrite to replace beginning of %{REQUEST_URI} for Slim Framework

I have a web directory structure like so:
root
/content
/plugins
/myplugin
/Slim (folder containing Slim Framework)
index.php
/other_folder_1
/other_folder_2
.htaccess
index.html
I'm interested in what to specify in my .htaccess file in order to refer to a directory that isn't actually present on the server, but actually point to the Slim app in the /myplugin directory.
Here are a few example URLs, which I'd like users (or myself) to be able to use in the browser's location bar, or link with in documents:
1. http://example.com/nonexistent_dir
2. http://example.com/nonexistent_dir/info
3. http://example.com/nonexistent_dir/info/details
I'm trying to rewrite these URLs to the following:
1. http://example.com/content/plugins/myplugin/index.php
2. http://example.com/content/plugins/myplugin/index.php/info
3. http://example.com/content/plugins/myplugin/index.php/info/details
...which would all actually be handled by the index.php Slim Framework app in the /myplugin directory. It's important the apparent URLs remain as they appear in the first example, without being changed in the location bar.
Here's what is currently in the .htaccess file in the root directory:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/schedule [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /content/plugins/myplugin/index.php [QSA,NC,L]
</IfModule>
This redirects all 3 of the test examples to http://example.com/nonexistent_dir, using the / route. So my thought is that I should be capturing everything after the nonexistent_dir, whether it be something or nothing, and appending it to the end of the RewriteRule somehow. But I don't understand how.
I realize that using parentheses around an expression will enable me to use the contents as a variable, referred to it with $1 (or $2, $3... for multiples), but I don't know how to apply it to this solution.
Any help will be most appreciated.
Try:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^nonexistent_dir(/.*)?$ /content/plugins/myplugin/index.php$1 [L]
Slim actually discards the base directory, and sets $env['PATH_INFO'], taking the content of this variable to match against the specified routes.
For example, lets take a /sub/index.php (Slim index file) and this rewrite rule:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^somedir(/.*)?$ /sub/index.php$1 [L]
...and this route specification:
$app->route('/info', function() use ($app) { ... });
So, with a GET request to /somedir/info, Slim strips /somedir from REQUEST_URI and sets $env['PATH_INFO'] with value /info (this is actually done in the constructor of \Slim\Environment class).
Later, the Router class will match /info and execute the closure function.
If you want to pass parameters via url, the route would be, for example:
$app->get('/info/:param', function($param) use ($app){ ... })

Mod_ReWrite regex unknown variables count?

i store uploaded files at /storage/ this way
public-adam-luki-uploads-123783.jpg
park-hanna-adel-propic-uploads-787689.jpg
the '-' count unknown because it slice the pic description
i want my users to be able to access it as
http://site.com/public/adam/luki/uploads/123783.jpg
http://site.com/park/hanna/adel/propic/uploads/787689.jpg
i think it is the same problem here
mod_rewrite with an unknown number of variables
but i can't do it because i'm new to mod_rewrite module
i hope you can help me guys with the right rewriterule
The question you link to doesn't actually do what you are trying to do (although the principle is the same) what they do is convert the url to GET variables.
If all you want to do is convert / to - then you can use a simple rewrite rule that will run in a loop:
ReWriteRule ^(.*)/(.*)$ $1-$2 [L]
There are of course a few caveats to that...
Firstly, even if you are trying to get to a real directory/file the rule will still switch out / and - and leave you with a 404. You can get around that by adding conditions; to stop it rewriting real files:
RewriteCond %{REQUEST_FILENAME} !-f
You would do better however to limit the matches to only images (jpgs):
RewriteCond %{REQUEST_FILENAME} !-f
ReWriteRule ^(.*)/(.*)\.jpg$ $1-$2.jpg [L]
Preferred Solution
ReWriteCond %{REQUEST_FILENAME} !-f
ReWriteRule ^images/(.*)/(.*)uploads[-/](\d+)\.jpg$ images/$1-$2uploads-$3.jpg [L]
RewriteCond %{REQUEST_FILENAME} !-f
ReWriteRule ^images/(.*)$ storage/$1 [L]
This solution requires you to use urls like:
http://site.com/images/park/hanna/adel/propic/uploads/787689.jpg
The pseudo directory images means you can be sure that the url is actually one that you want to redirect and it doesn't break other images/links on your site.
The above rules take a url (like the example above) and transforms it like so:
images/park/hanna/adel/propic/uploads/787689.jpg <--- Original
images/park-hanna/adel/propic/uploads-787689.jpg
images/park-hanna-adel/propic/uploads-787689.jpg
images/park-hanna-adel-propic/uploads-787689.jpg
images/park-hanna-adel-propic-uploads-787689.jpg
storage/park-hanna-adel-propic-uploads-787689.jpg <--- Final

Categories