Disclaimer: I type slowly and tend to have multiple tabs open, so this post may appear to ignore one or more preceding posts.
empty your browser cache
If you have the option of testing somewhere other than your live site-- for example a test site or MAMP/WAMP --add this element to your config or htaccess:
ExpiresActive On
ExpiresByType text/html "access"
Where I say "text/html" add any extensions you actually use for pages. This means that if you've got compliant browsers, they will make a fresh request for each page every time. (You will still need to refresh or, worst case, empty the cache if you've tweaked the css. This has bitten me many times ;))
Options +Indexes
Do you really want this for your entire site? You might have something like an image directory that you want users to be able to paw through at will, but normally people don't want to give free access to all users everywhere. In practice, the option only kicks in if a directory has no named index file (index.html, index.php, whatever). But you never know where a human user might decide to snoop.
# Internally rewrite extensionless URL to corresponding .php
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{QUERY_STRING} ^(.*)$
RewriteRule ^(.*)$ $1.php?%1 [NC,L,QSA]
What are you trying to do here? The last two lines are identical to
RewriteRule ^(.*)$ $1.php [L]
with no second condition and no QSA, since "reappend the query" is mod_rewrite's default behavior anyway. And [NC] isn't needed since you are not matching literal text in the pattern. If you
were matching literal text, you still wouldn't use [NC] in a rule creating an internal rewrite, because it creates the option of Duplicate Content. (Exception: [NC] is OK if the rewrite points to a php file that will issue a 301 or 404 on its own behalf when casing is wrong.)
A more serious problem is that the rule doesn't exclude requests that
already end in .php. 99 times out of 100 you can express the pattern as
^([^.]+)$
The anchors are now essential, where before they weren't needed. The 100th time, of course, is if your directory names contain literal periods. This is perfectly legal-- see apache's own site for examples ;) --but you can save yourself a ### of a lot of trouble if you stick strictly to alphanumerics, lowlines and hyphens.
# Externally redirect (only) direct client requests for .php URLs to extensionless URLs:
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /([^/]+/)*[^.#?\ ]+\.php([#?][^\ ]*)?\ HTTP/
RewriteRule ^(([^/]+/)*[^.]+)\.php http://example.com/$1 [R=301,L]
This is a little awkward. The pattern
([^/]+/)*[^.]+
means that the last part can theoretically contain slashes. In practice it will never happen, thanks to Regular Expressions being greedy by nature. But, again, if your directory names don't contain literal periods, you can express the whole pattern as simply [^.]+
Main Issue The rules as given in the OP are in the wrong order. The overall grouping goes like this:
FIRST group rules in order of severity. That means that any access-control rules ([F] flag) come first. Then [G] if any. Then redirects ([R=301] flag). And finally the internal rewrites.
And, er, super-finally, rules that don't change anything at all and don't set an [L] flag, such as cookies. These are rare. THEN within each group, go from most specific to most general. Ordinarily that means your very last redirect is domain-name canonicalization: with or without www. The Condition here should say !^(example\.com)?$ if you're using the without-www form. There are further complications if you've got multiple domains passing through the same htaccess. Do you?
The second-to-last redirect is one the OP doesn't have: the "index.hmtl" or "index.php" redirect.
If you're going extensionless, there is an additional redirect for requests ending in .php. Now rule ordering becomes crucial, because
Requests for /blahblah/index.php have to get redirected to /blahblah/ alone
while
Requests for /blahblah/somename.php have to get redirected to /blahblah/somename
So the extension-redirect has to come
after the index redirect but
before the domain-name redirect.