Detect IE6 and redirect to compatible site in CRE Loaded

There are tons are beautiful CRE Loaded sites--if you are ever in the market for one, I personally am a huge fan of AlgoZone and RocketTheme for CRE Loaded and Joomla themes. But one of the downsides of the nicer themes is compatibility issues. For those of you who have not jumped on the CSS bandwagon, or for others of you unaware of the "old days" of web development, there has been a huge shift over time in the way styles are applied to sites.

In the old days, tables were your only tool. Whatever you needed to do, you created tables, nested tables, and played with table attributes. Tables could do a lot, but the final code for a complex page was all but incomprehensible, and forget trying to debug someone else's. It could be a real mess to find and fix errors, or to try to add new elements into pages.

The shift over time has been to use CSS styles applied to DIV tags. This keeps a great deal of presentation details separate from the pages, allow for page content to be developed apart from really complex styles. In addition to separating the actual code, this also provides great ways to take a page and render it completely differently based completely on the CSS applied. Content management systems in particular make extensive use of this because their individual widgets can display simple content, and the CSS applied can really change the output. This would have been a very difficult job in the old table days.

So back to CRE Loaded styles. I recently was working on putting a new template on a storefront, when one of my team members thought to look at the in IE6. The results were horrific. The site not only looked bad, elements such as the product menu just didn't show up. We ran some stats and found to our surprise that nearly 10% of potential customers coming to the store use IE6. So something had to be done.

I decided the easiest solution was to set up another domain, check the browser type, and then redirect IE6 users to the other domain. Connection information in CRE Loaded would stay the same, so both sets of users would share the same database, and the job would be trivial.

So, I searched the code and found a good place to put the IE6. The first step was the put the following in includes/application_top.php:

// first.....since IE6 performs very badly with our template....
// detect the browser and send all those poor souls to the
// old template

$a_browser_data = browser_detection('full');
if ( $a_browser_data[0] == 'ie' )
if ( $a_browser_data[1] <= 6 ) { $url = (!empty($_SERVER['HTTPS'])) ? "https://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'] : "http://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; $url = str_replace("", "", $url); header('Location: ' . $url ) ; } }

I found some great code to detect the browser type here. I put that code in includes/browser_detect.php.

Then I went to test, and discovered that half the time I clicked on things, I was being redirected to the old site. After some seaching, I found that the domain is actually stored in a few pages of the php script. I changed those, and then I was staying consistently in the new site. Perfect.

Next I went into the admin side of the new site, and changed the template. Oh wait. The default template is stored in the database! The database that I am sharing between the sites! Time for more analysis.

I searched and searched through the code and discovered that DEFAULT_TEMPLATE was the variable that I needed to set, but DEFAULT_TEMPLATE was not set anywhere in the code. I realized my lack of PHP knowledge was hurting me, so I started looking up constant definition in PHP. It became clear that the value was being set somewhere, but searching the codebase had turned up nothing. Then I realized there must be somewhere in the code that all the configuration variables were being pulled in from the database and constants were being created for them.

After more searching, I discovered the code I expected was in includes/application_top.php, not far from where I had inserted the above lines:

// set application wide parameters
$configuration_query = tep_db_query('select configuration_key as cfgKey, configuration_value as cfgValue from ' . TABLE_CONFIGURATION);
while ($configuration = tep_db_fetch_array($configuration_query)) {
define($configuration['cfgKey'], $configuration['cfgValue']);

PHP does not allow you to redefine the constants there was no way to let this loop occur and then redefine the value later. Instead, you could either put an if clause in the code, look for DEFAULT_TEMPLATE, and set the value there, or instead set your override value first, then check as you are looping to see if the value is already defined, and only define it if it is not. It was late at night, so I chose the first. The more bulletproof way would really be to set up an include to define overrides and then put the conditional logic in this loop to skip any values that are currently defined. When I have some free time I will definitely refactor this code.

Anyone ever try to do anything like this? Let me know if you had a better solution. Thanks!

The Infamous "Access Denied" AJAX Error

A client of mine had a website issue--the red X in the lower left in IE saying an error had occurred. I dug in a little and discovered the famous pop-up that you have probably seen if you have done any amount of AJAX development:

The error message can change slightly depending on your browser, but you will get some variation of "This page is accessing information that is not under its control. This poses a security risk. Do you want to continue?" or "Access denied" or "Access is denied."

Essentially, here is the problem. It is not uncommon, especially in large corporate environments, for the web services being called to actually live in a different domain from the page that is rendered. Browsers consider it a security hole to be displaying data from site X and be performing an XmlHttpRequest against site Y. And this is probably for the best.

To remedy this situation, you need to either (1) get your application and web services on the same domain or (2) provide some kind of proxy that allows for calling the web service on the other domain within your own domain.

Sometimes this is easier said then done, but I was very lucky in my case. My client actually was using a domain name that resolved to the same domain the page was being served up from. They had created separation in case the web services and web sites were ever migrated to different machines. So, it was a simple matter of using the same domain for the services used by the web site.

The client is actually moving in the direction of making all their machines the same, at least for this collection of web apps and services. It is not often a resolution to a problem is so easy. Anyway, this is something to keep in mind when you get the "access denied" error on an AJAX-enabled site.

Good luck out there!

Draw text faster with CGContextShowTextAtPoint

The FindIt puzzles have turned out pretty well, but there are still a few things I am not happy with. The things that bother me most--slow load times on the puzzle selection page and on the puzzle screen. The load time on the puzzle selection page is not horrible--usually 2 to 3 seconds. But my goal is to release an update to FindIt Premium that will include all the puzzle packs to date. If I am seeing 2-3 seconds for 10 puzzles, what will it be like at 30? This clearly needs to be improved.

Luckily, this part was easier to figure out. To get the game out the door, I used text files for the data. With some time to step back and get the game cleaned up, I changed the puzzle metadata over from flat files to SQLite and Core Data. This has had a small impact on load time--around 2 seconds. Not that significant for just 10 puzzles, but in my profiling, and I seeing a significant increase as the number of puzzles increases. So my update for this should be coming out soon.

But what about the main page load time? Again, I performed some analysis to determine what was the bottleneck. I had assumed loading the page data from files again was the problem. So I started commenting out code and testing. Sure enough, the puzzle data loaded very quickly from a single file. That wasn't it.

Next I tried changing my looping logic, changed from a dynamically allocated NSMutableArray to an array I preallocated to the correct size, and even changed my short-cutting logic to decide whether to write out a letter or not. None of these were the culprit.

But then I commented out my writing the letters to the screen with NSString drawInRect. Immediately, even my largest puzzles (85x85) loaded in a second. So I set out to find a more efficient way to write out text. As I learned more, I saw that drawInRect is really trying to do a lot for you--like using the output area of the text for alignment and clipping. I am just trying to write out a single letter, so this was all unnecessary overhead.

If you are doing something like this, try CGContextShowTextAtPoint. This is a much more high-performance function that is not trying to align the text or perform clipping--it is just writing out the letters the way you tell it to. For me, this was perfect, and much more efficient. I noticed load time on even the largest puzzles was much more efficient, around 2 seconds or so, down from as many as 5.

This is also good news since I plan to release an iPad version of FindIt. Sure, the processor is going to be faster, but I also plan to display a lot more on a page. Considering that, I wanted to be sure to have the efficiency tweaked for optimal performance.

Hopefully this helps you too! Good luck with your iPhone and iPad development!

Caveat on Caching in ASP.NET

If you are working with the System.Web.Caching.Cache component in ASP.NET, there is something you should probably know. The cache is not a virtual location where data is being stored; instead, the cache is a collection, and as such, the objects being put into it are being put in by reference, not by value.

I was working with a client today and they were experiencing an issue that no one could figure out. I dug into it, and discovered that they were doing the following:

1) Creating an System.Xml.XmlDocument object and loading it with data
2) Inserting this object into the cache
3) Removing some nodes from the object

Following a page transition, they were pulling the document back out of the cache, and they could not understand why nodes seemed to be disappearing. This is when I explained how the cache works, and that is it not saving a representation of the object when it is inserted into the cache, but rather putting the object into the cache collection.

Just a good thing to know if you plan to be caching and changing objects. Good luck out there!