1

I'm transferring an e-commerce site to a new shopping basket. For some reason the old site used dashes for categories but underscores for products.

So the full URL of a product looks like this:

http://www.example.com/Engineering-Common-Bricks/65mm_Class_B__Solid_Engineering_Brick__Price_Each

The new basket uses dashes for everything so I need to rewrite incoming URLs such as the above to the following:

http://www.example.com/Engineering-Common-Bricks/65mm-Class-B--Solid-Engineering-Brick--Price-Each

I know there is a lot of material on rewriting underscores to dashes but none of them seem to be working for me.

I am using Opencart which comes with an existing set of rewrite rules so perhaps these are interfering with the new rules I'm trying to add.

The existing .htaccess is as follows:

Options +FollowSymlinks

# Prevent Directoy listing 
Options -Indexes

# Prevent Direct Access to files
<FilesMatch "\.(tpl|ini|log)">
 Order deny,allow
 Deny from all
</FilesMatch>

# SEO URL Settings
RewriteEngine On
# If your opencart installation does not run on the main web folder make sure you folder it does run in ie. / becomes /shop/ 

RewriteBase /
RewriteRule ^sitemap.xml$ index.php?route=feed/google_sitemap [L]
RewriteRule ^googlebase.xml$ index.php?route=feed/google_base [L]
RewriteRule ^download/(.*) /index.php?route=error/not_found [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !.*\.(ico|gif|jpg|jpeg|png|js|css)
RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]

Any help or direction would be gratefully received.


Updating the .htaccess as per Esa's answer to the following:

Options +FollowSymlinks
Options -Indexes

<FilesMatch "\.(tpl|ini|log)">
 Order deny,allow
 Deny from all
</FilesMatch>

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^_]*)_([^_]*_.*) $1-$2 [N]
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]

RewriteBase /
RewriteRule ^sitemap.xml$ index.php?route=feed/google_sitemap [L]
RewriteRule ^googlebase.xml$ index.php?route=feed/google_base [L]
RewriteRule ^download/(.*) /index.php?route=error/not_found [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !.*\.(ico|gif|jpg|jpeg|png|js|css)
RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]

Works fine for:

http://example.com/this_is_a_category

But causes Apache to crash on:

http://example.com/this_is_a_category/this_is_a_product

2 Answers 2

1

Since RewriteRules are for regular expression matches instead of replaces, the first line in

RewriteRule ^([^_]*)_([^_]*_.*) $1-$2 [N]
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]

is not repeated. Therefore you will need one line for every amount of underscores, e.g.

RewriteRule ^(.*)_(.*)_(.*)_(.*)_(.*)_(.*)_(.*)$ /$1-$2-$3-$4-$5-$6-$7 [R=301,L]
RewriteRule ^(.*)_(.*)_(.*)_(.*)_(.*)_(.*)$ /$1-$2-$3-$4-$5-$6 [R=301,L]
RewriteRule ^(.*)_(.*)_(.*)_(.*)_(.*)$ /$1-$2-$3-$4-$5 [R=301,L]
RewriteRule ^(.*)_(.*)_(.*)_(.*)$ /$1-$2-$3-$4 [R=301,L]
RewriteRule ^(.*)_(.*)_(.*)$ /$1-$2-$3 [R=301,L]
RewriteRule ^(.*)_(.*)$ /$1-$2 [R=301,L]

replaces 1-5 underscores, resulting in

/Engineering_Common_Bricks/65mm_Class_B-Solid-Engineering-Brick-Price-Each

so for 9 replaces you'll need a few more lines to the top. The pattern will be same, but the longer expressions needs to become first.

However, this replaces all underscores with dashes (and redirects after the last one), so you must not have any file names containing underscores. You could also prevent that happening by adding a RewriteCond directive BEFORE these rules:

RewriteCond %{REQUEST_FILENAME} !-f
5
  • Many thanks for your response. If I use this on it's own it works perfect but when combined with my existing .htacces: If i put this after my existing directives it doesn't seem to do anything. If i put it before them it crashes apache.
    – jx12345
    Mar 13, 2015 at 13:50
  • Further: it works fine on its own for: 'test.local/this_is_a_test' but crashes on 'test.local/this_is_a_test/this_is_a_test'
    – jx12345
    Mar 13, 2015 at 13:59
  • It must be after RewriteEngine On and before any RewriteRule that may match earlier but shouldn't. Mar 13, 2015 at 14:05
  • Notice the word before. Conditions must be introduced before the rules. Mar 13, 2015 at 14:13
  • Ah yes, apologies, but even with the condition before it crashes for: 'example.com/this_is/a_test
    – jx12345
    Mar 13, 2015 at 14:20
0
RewriteRule ^([^_]*)_([^_]*_.*) $1-$2 [N]
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]

Works fine for:

http://example.com/this_is_a_category

But causes Apache to crash on:

http://example.com/this_is_a_category/this_is_a_product

To resolve this you simply need to add the DPI (Discard Path Info) flag to the first RewriteRule directive:

RewriteRule ^([^_]*)_([^_]*_.*) $1-$2 [N,DPI]
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]

The "Apache crash" is caused by an endless rewrite loop. The N flag causes the ruleset to loop (which is required in this scenario in order to replace all bar one of the underscores), however, path-info from the original request is appended to the rewritten URL on each iteration (by design).

In the first example, with just a single path segment, there is no additional path-info, so nothing is appended. However, the second example contains additional path-info, namely /this_is_a_product (everything after the first path segment that does not map to a physical filesystem path) - which itself contains underscores. This is the problem. Each iteration of the loop replaces just one underscore, but each iteration of the loop is also appending 3 more (in this example)! So, it's never able to complete the task of replacing all (bar one) of the underscores!

If you enable rewrite logging, the error log will report something like:

/this_is_a_category/this_is_a_product
/this-is_a_category/this_is_a_product/this_is_a_product
/this-is-a_category/this_is_a_product/this_is_a_product/this_is_a_product
/this-is-a-category/this_is_a_product/this_is_a_product/this_is_a_product/this_is_a_product
/this-is-a-category/this-is_a_product/this_is_a_product/this_is_a_product/this_is_a_product/this_is_a_product
:

As you can see, this very quickly spirals out of control. By default, it will stop after 32,000 iterations(!), but your server is probably running out of resources before then; hence the crash. On Apache 2.4.8+ you can limit the number of iterations, eg. N=10 - although this won't solve your problem, it will stop your server from crashing!

AFAIK, the DPI flag (Apache 2.2.12+) was specifically created to solve this issue. It discards the original path-info from the rewritten URL, so it's not re-appended each time, thus preventing the endless rewrite loop.

Reference:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .