views:

1064

answers:

7

Thank you one and all for your patience and help. I am completely restating the question because it is getting quite long from all my revisions. I have an PHP MVC framework with 4 entry points:

from the root:
index.php
index-ajax.php
admin/index.php
admin/index-ajax.php

I needed a .htcaccess file that would take any request and rewrite it to the corresponding file based on the url. The long url would be index.php?rt=cms/view/15 and I wanted it to be index/cms/view/15. That part is pretty much done except for one gotcha.

Here is my .htaccess file now:

# htaccess file for framework - GOOD
Options +FollowSymLinks

# Turn on the mod_rewrite engine - GOOD
RewriteEngine On

# Hide indexes - GOOD
Options -Indexes

# If a file is not one of these, continue processing. - GOOD
RewriteRule \.(css|js|jpg|jpeg|png|gif|ico)$ - [L]

# RewriteRules for folder index files
#RewriteRule ^(index)?(.php)?$ index.php [L] - GOOD
#RewriteRule ^admin(/?)(index)?(.php)?$ admin/index.php [L] - GOOD

# RewriteRules for admin folder arguements - going from more specific to less
RewriteRule ^admin/ajax/[A-Za-z0-9-_/]*$ admin/index-ajax.php?rt=$1 [L]
RewriteRule ^admin/[A-Za-z0-9-_/]*$ admin/index.php?rt=$1 [L]

# RewriteRule for root ajax file
RewriteRule ^ajax/[A-Za-z0-9-_/]*$ index-ajax.php?rt=$1 [L]

# RewriteRule for root file - by here, it is not ajax or admin related, so only
# possible option left if the root index file
RewriteRule ^[A-Za-z0-9-_/]*$ index.php?rt=$1 [L]

I have made a simple site with two folders - 'root' and 'root/admin', and inside of each of those a css,images, and javascript folder with some dummy content. There is an index.php and index-ajax.php file inside of 'root' and 'root/admin' that simple outputs whatever the url argument is and uses a css, js, and image file from each of the folders.

The problem I have now is that if I do a url like index/blah or /admin/index/blah, then the page presents right and the argument is right. However, when I do a url like index/blah/view or admin/index/blah/view then the arguement is right (?rt=blah/view) but the page presents wrong because the css/js/images file go to index/blah/[css] instead of index/[css].

Any ideas on how to handle this? I allowed css/js/image files to go through as is via the .htaccess so there would be less work there.

+2  A: 

Well, it looks pretty good, except those $ you put in the middle would kinda ruin it; a $ marks the end of the regex so I don't think it would work correctly.

Also, I think you may need to escape the forward slashes, but I'm not sure.

Eugene Bulkin
I just don't type fast enough obviously.
seth
I thought the $ signs served as placeholders to transfer the value from the left URL into the right URL?
Robert DeBoer
He means the $ in the first part, between the /
seth
Yep, for sure the $ sign was an issue
Robert DeBoer
A: 

The last line should likely be:

RewriteRule ^/([A-Za-z\-_]+)/([A-Za-z\-_]+)/([A-Za-z0-9\-_]+)$ /index.php?com=$1&action=$2&val=$3 [L,R]
lemonad
+9  A: 

Are you sure that everything should be handled by index.php? What about static files such as images/css etc.?

Here is an alternative method which may interest you. You can forward any URL which does not already exist as a file or directory to your index.php file and then parse the URL there, e.g. [domain.com]/cms/view/15 would be rewritten to [domain.com]/index.php/cms/view/15 . You need to have the apache directive AcceptPathInfo set to On for this work.

.htaccess:

RewriteEngine On
#check url is not a valid file
RewriteCond %{REQUEST_FILENAME} !-f
#check url is not a valid directory
RewriteCond %{REQUEST_FILENAME} !-d
#rewite anything left
RewriteRule ^(.*)$ index.php/$1 [L]

index.php

$path = trim($_SERVER['PATH_INFO'], '/');
$pathParts = explode('/', $path);

if (isset($pathParts[0])) {
    $com = $pathParts[0];
} else {
    $com = 'defaultcom';
}

//$com[1] will be 'view' if supplied
//$com[2] will be 15 if supplied

I like this approach because you are not forced to define and understand the URL in the apache configuration, but you can do most of the work in PHP.

Edit

You could use this as your .htaccess instead, this would redirect any request with an extension not in the list to your PHP script. The RewriteCond should stop requests to the admin folder from being rewritten.

RewriteEngine On
#don't rewrite admin
RewriteCond %{REQUEST_URI} ^admin/
#rewrite anything with a file extension not in the list
RewriteRule !\.(js|gif|jpg|png|css|ico)$ /index.php [L]
Tom Haigh
Images and CSS can also be handled by PHP. I do it regularly for something quite silly and certainly doable by reconfiguring Apache (which you may or may not be able to do, though): I put a file called `static.php` which only adds expiry headers to files served from `static_files/`.
Ivan Vučica
Thank you, I never thought about images,css,etc. The user would need direct access to those files for the end html output - correct?However, I don't want user to be able to access any of the PHP files - they don't need to be able to run any except index.php - all other php files are used by the framework and won't work if run out of context anyways.
Robert DeBoer
Thanks again. For the rewrite anything that is not in the list, can you just have it continue on to the next rewrite rule. So, if the files is not in the list, then continue on to the next rewrite rule, which would then rewrite the index.php url?
Robert DeBoer
It should do that anyway? I'm not sure what you mean.
Tom Haigh
I'm sorry for the confusion. I'm pretty sure I need the following:Disable indexes, Direct access for any jpg,png,gif,css,js file, Rewrite rule for index.php, Rewrite rule for index-ajax.php, Rewrite rule for /admin/index.php, Rewrite rule for /admin/index-ajax.php,So that index.php?..... becomes .com/[part1]/[part2]/[part3] ; admin/index.php?.... becomes .com/admin/[part1]/[part2]/[part3] ; and the same for the corrosponding index-ajax.php files.
Robert DeBoer
+2  A: 

I would do it like Tom Haigh said and parse the requested URL path with PHP:

// extract the URL path
$_SERVER['REQUEST_URI_PATH'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// extract the path segments
$segments = explode('/', trim($_SERVER['REQUEST_URI_PATH'], '/'));
// expected parameters and its default values
$params = array(
    'com'    => 'default',
    'action' => 'default',
    'val'    => 'default'
);
foreach ($params as $key => $val) {
    if (isset($segments[0])) {
        $_GET[rawurldecode($key)] = rawurldecode(array_shift($segments));
    } else {
        break;
    }
}
$restOfTheURLPath = implode('/', $segments);
var_dump($_GET);

And the mod_rewrite rules:

# redirect every request, that cannot be mapped to an existing file, to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^admin(/|$) admin/index.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

Edit    In reply to your comment and now edited question: You can exclude the directories you want with a rule in front that ends the rewrite process:

RewriteRule ^(css|images)/ - [L]
RewriteRule ^admin/index\.php$ - [L]
RewriteRule !^index\.php$ index.php [L]
Gumbo
Thank you, I never thought about images,css,etc. The user would need direct access to those files for the end html output - correct?<br />However, I don't want the user to be able to access any of the other PHP files - they don't need to be able to run any except index.php - all other php files are used by the framework and won't work if run out of context anyways.<br /> I'm developing an MVC framework, so index.php serves as the entry point that processes the user request from the URL. Or am I missing something in my thought process (it's possible :).
Robert DeBoer
Thanks, I have decided to do a dual approach like you suggested. A GET variable will contain the path, which will already contain the slashes which separate the different components of the path. I will just use mod_rewrite to get rid of the "?rt=" in the public URL and use PHP to parse the GET variable. This should be easier than trying to do all the breaking with mod_rewrite. My framework already deals with breaking the GET variable apart, so this will also be the quickest adaptation of mod_rewrite. I just need to make sure my .htaccess file is right.
Robert DeBoer
A: 

So the images/css/etc break because you're using a relative URL in the HTML, and now that you're going more nested they're breaking? With some other MVC setups I encountered, there's usually a "root path" variable of some sort, set by the Controller based on the request URL, and used by the View component to prefix all paths to assets like images/styles/scripts.

That turns your .htaccess problem into a Controller/PHP problem, which if that's an acceptable solution for you, would involve your Controller doing something like exploding the request URL on "/"s, counting the number of resulting pieces, and constructing a relative URL from the result.

I did a similar thing for a custom MVC setup I did; check out http://avogadro.ws/hosted/skel/public/results/school-1 and look at the head source for the CSS locations. The CSS for that page is actually at the http://avogadro.ws/hosted/skel/ level, which the Controller builds into a relative string.

MidnightLightning
Yes. The image/css/js/etc file links in the html are relative to that document. There are some external resources that every result from the MVC will have (global stylesheet, html skeleton images, etc).The problem seems to be that in my .htaccess I have allowed any file request with a css,js, or image extension to pass through untouched - because I thought these resources wouldn't need any processing, they are used as is. But now that the url is nested, it thinks they are in a folder called "blah" when "blah" is actually the controller, so the url goes through to an nonexistent folder.
Robert DeBoer
I think it's good that your image/css/js/etc files are bypassing the .htaccess. My solution above corrects the "it thinks they are in a folder called 'blah'" problem by adding a prefix to the URL. In my example above, the `/hosted/skel` folder actually exists. However, `public/results/` does not. If the 'school-1' page referred to a CSS file as "main.css", it would try to fetch `/hosted/skel/public/results/main.css`. So, I change the HTML to refer to the CSS file as "../../main.css", which puts it at `/hosted/skel/main.css`, which is where the file actually is.
MidnightLightning
+3  A: 

Unless I've missed the point of your question (I am pitching in after your question edit), it sounds as if you need to fix relative image/css paths for URLs which are many-folders deep. Simple solution, don't use relative image paths! Just start your image/css links with a forward-slash..

<img src="/img/logo.png" alt="something" />

I've always found it's an easier approach when you have a single template powering many pages which unpredictable folder depths. As long as you have a test URL for local development it works fine for local and production servers.

simonrjones
I've found this approach works well for any rewrite path problems I've had in the past.
justinl
+1  A: 
# htaccess file for framework - GOOD
Options +FollowSymLinks

# Turn on the mod_rewrite engine - GOOD
RewriteEngine On

# Hide indexes - GOOD
Options -Indexes

# If a file is not one of these, continue processing. - GOOD
RewriteRule ^images/([A-Za-z0-9\-_]*)\.(css|js|jpg|jpeg|png|gif|ico))$ - 
RewriteRule ^images/admin/([A-Za-z0-9\-_]*)\.(css|js|jpg|jpeg|png|gif|ico))$ - 
RewriteRule ^admin/(([A-Za-z0-9\-_/]*)/([A-Za-z0-9\-_]*)\.(css|js|jpg|jpeg|png|gif|ico))$ admin/images/$3.$4 [L]
RewriteRule ^(([A-Za-z0-9\-_/]*)/([A-Za-z0-9\-_]*)\.(css|js|jpg|jpeg|png|gif|ico))$ images/$3.$4 [L]

# RewriteRules for folder index files
#RewriteRule ^(index)?(.php)?$ index.php [L] - GOOD
#RewriteRule ^admin(/?)(index)?(.php)?$ admin/index.php [L] - GOOD

# RewriteRules for admin folder arguements - going from more specific to less
RewriteRule ^admin/ajax/([A-Za-z0-9\-_/]*)$ admin/index-ajax.php?rt=$1 [L]
RewriteRule ^admin/([A-Za-z0-9\-_/]*)$ admin/index.php?rt=$1 [L]

# RewriteRule for root ajax file
RewriteRule ^ajax/([A-Za-z0-9\-_/]*)$ index-ajax.php?rt=$1 [L]

# RewriteRule for root file - by here, it is not ajax or admin related, so only
# possible option left if the root index file
RewriteRule ^([A-Za-z0-9\-_/]*)$ index.php?rt=$1 [L]

The above .htaccess works when testing with your directory and file setup on a Linux/apache server

I replaced you ignore-images row with a rewrite that makes a request for an image in /ajax/hello/world/images go to /images and if it's in the admin folder it will go to /admin/images

Simon Byholm