1

I need a help with blog which is in core php and want to create a pretty url using .htaccess

the main index.php which is a blog page and also post page which display a single blog post based on IF statements

so what I want to achieve is to make a visit to https://example.com/?slug=my-blogpost-url-slug load as https://example.com/my-blogpost-url-slug

there's already an IF statement on example.com/index.php which check if there's an available post slug and if not it redirect to 404

Already a visit to example.com loads example.com/index.php

below are the htaccess i code i have already

## force https
RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://example.com/$1 [R,L]

## remove .php n co
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]

## remove www 
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.example.com
RewriteRule (.*) https://example.com/$1 [R=301,L]

## error documents
ErrorDocument 404 https://example.com/err?code=404
ErrorDocument 500 https://example.com/err?code=500
zaacwiliam
  • 11
  • 1

1 Answers1

1

so what I want to achieve is to make a visit to https://example.com/?slug=my-blogpost-url-slug load as https://example.com/my-blogpost-url-slug

It's actually the other way round... the user visits https://example.com/my-blogpost-url-slug (and should be the target of your links) and it loads as if the request is for https://example.com/index.php?slug=my-blogpost-url-slug - which is the URL being rewritten to. Note that I've included index.php in the rewrite, otherwise you are dependent on mod_dir making an additional subrequest for the DirectoryIndex document.

This is called a front-controller pattern. index.php is the "front-controller".

## remove .php n co
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]

But first, you need to "correct" your rule (above) that ## remove .php n co to first check that the corresponding .php file exists before rewriting the request, otherwise this will conflict with the front-controller pattern that we need to write in the following section to rewrite your "pretty" URLs.

The "problem" with the above rule is that it rewrites anything that does not exist, which is basically what the front-controller needs to do.

The above should be written like this instead:

RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^.]+)$ $1.php [L]

You don't need to backslash escape literal dots when used inside regex character classes as they carry no special meaning in this context. The NC flag is redundant since the regex ^([^.]+)$ already matches both upper and lowercase letters.

The front-controller pattern

This is the bit you are really asking about...

We can avoid additional file system checks by restricting the regex that the rule matches against. ie. Just URLs of the form /my-blogpost-url-slug where the slug cannot contain slashes or dots (which would conflict with actual files).

You can do this with an additional rule at the end of your file:

# Rewrite "/<slug>" to "/index.php?slug=<slug>"
RewriteRule ^[^/.]+$ index.php?slug=$0 [L]

The $0 backreference contains the entire URL-path matched by the RewriteRule pattern. Note there is no slash prefix on this URL-path. Include the QSA flag if you are expecting an additional query string on the requested URL.

When calling the document root (ie. homepage) then index.php is called (as you state), but the slug URL param will be omitted.

Note you do not need to repeat the RewriteEngine directive multiple times in the file, as you have done. For readability, this should occur once at the top of the file.

Summary

The order of your rules is also incorrect. The canonical redirects should be first. For example, your rules should look like this (noting the additional points regarding the ErrorDocument directives below):

## error documents
ErrorDocument 404 /err.php
ErrorDocument 500 /err.php

RewriteEngine On

## force https
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

## remove www 
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteRule (.*) https://example.com/$1 [R=301,L]

## remove .php n co
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^.]+)$ $1.php [L]

#### FRONT-CONTROLLER ####
# Rewrite "/<slug>" to "/index.php?slug=<slug>"
RewriteRule ^[^/.]+$ index.php?slug=$0 [L]

Aside:

ErrorDocument 404 https://example.com/err?code=404
ErrorDocument 500 https://example.com/err?code=500

You should be using a root-relative URL-path in the ErrorDocument directive, not an absolute URL, as you have written here. This will trigger a 302 (temporary) redirect to the error document and the HTTP response status will be lost.

You should also be using the full filename, which I assume is err.php and not rely on your other rules for this.

You then don't need to pass the HTTP status code in the code URL parameter as this will be accessible in the $_SERVER['REDIRECT_STATUS'] superglobal in your PHP code.

So, this should be written as follows:

ErrorDocument 404 /err.php
ErrorDocument 500 /err.php

NB: You are unlikely going to catch real 500 (Internal Server) errors having defined the ErrorDocument late in .htaccess.

The order of these directives in the .htaccess file does not really matter, however, it is more logical to define these first, at the top of the file, not at the end.

MrWhite
  • 43,179
  • 8
  • 60
  • 84
  • Thanks or the response and correction but still not working example.com/?slug=test should load via example.com/test or better still example.com/post/?slug=test should load to example.com/post/test – zaacwiliam Sep 21 '22 at 17:26
  • @zaacwiliam To clarify (as stated in my answer) you need to actually be linking to and requesting the "pretty" URL. ie. As in your question, this would be `example.com/test`. If you are currently linking to `/?slug=test` in your HTML source then you need to actually change your links - this is not something you do in `.htaccess`. Is that what you are doing? – MrWhite Sep 21 '22 at 17:44
  • @zaacwiliam Are you perhaps changing an existing URL structure that has already been indexed by search engines or linked to by third parties, or is this a new site? If the former then you can implement an external redirect from the old to new URL, but otherwise this is not strictly necessary. – MrWhite Sep 21 '22 at 17:46
  • @zaacwiliam I've updated my answer with more information and a complete summary of the `.htaccess` file, with the solution in place. – MrWhite Sep 21 '22 at 17:47
  • @zaacwiliam "or better still example.com/post/?slug=test should load to example.com/post/test" - You have your description/terminology back to front... `/post/test` would be the URL being requested (this is the "pretty" URL that your users see). This is then internally rewritten to the underlying file that actually handles the request. eg `/post/index.php?slug=test` (assuming `/post/index.php` exists as a physical file). `/post/index.php?slug=test` is entirely hidden from the user. But for this to be "hidden" it must not appear anywhere in the client-side HTML. – MrWhite Sep 21 '22 at 17:51