Interchange Blog Archive
Using charge tag in Interchange's profiles, and trickiness with logic and tag interpolation order
One of the standard ways to charging in older versions of the Interchange demo was to do the charging from a profile using the &charge command. New versions of the demo store do the charging from log_transaction once the order profiles have finished, so it is not an issue there. I've come across quite a few catalogs where the &charge command is replaced with the [charge] tag wrapped in if-then-else blocks in an order profile. It had been so long since I had used &charge so I needed to lookup how options are passed to it, which may be why people tend to use the tag version instead of the &charge command. The problem here is that Interchange tags interpolate before any of the profile specifications execute, so if you have a [charge] tag in an order profile, it executes before any of the other checks, such as validation of fields.
Here's a stripped down example of where a profile will have tags executed before the other profile checks:
lname=required Last name required fname=required First name required &fatal=yes &credit_card=standard keep [charge route="[var MV_PAYMENT_MODE]" amount="[scratch some_total_calculation]"] &final=yes
In this situation even if lname, fname or the credit card number are invalid, charge will execute before all of those checks occur, calling your payment gateway with invalid parameters. This could even cause a weird state where a credit card was charged, but the order not placed because the last name check fails for example, after the charge is successful.
The way around this is either to move the credit card charging out of the order profile into log_transaction or use the &charge command like so:
&charge=[var MV_PAYMENT_MODE] amount=[scratch some_total_calculation]
Another situation where you should be careful is using if-then-else blocks, if you need to do a profile checks that are dependent upon the results of other calls in the profile then you will need to create a custom order check to do that processing, otherwise sections of your if-then-else may execute that are not intended to.
Tip: Find all non-UTF-8 files
Here's an easy way to find all non-UTF-8 files for later perusal:
find . -type f | xargs -I {} bash -c "iconv -f utf-8 -t utf-16 {} &>/dev/null || echo {}" > utf8_fail
I've needed this before for converting projects over into UTF-8; obviously certain files are going to be binary and will show up in this list, so manual vetting will need to be done before converting all your images over into UTF-8.
Safari 4 Top Sites feature skews analytics
Safari version 4 has a new "Top Sites" feature that shows thumbnail images of the sites the user most frequently visits (or, until enough history is collected, just generally popular sites).
Martin Sutherland describes this feature in details and shows how to detect these requests, which set the X-Purpose HTTP header to "preview".
The reason this matters is that Safari uses its normal browsing engine to fetch not just the HTML, but all embedded JavaScript and images, and runs in-page client JavaScript code. And these preview thumbnails are refreshed fairly frequently -- possibly several times per day per user.
Thus every preview request looks just like a regular user visit, and this skews analytics which see a much higher than average number of views from Safari 4 users, with lower time-on-site averages and higher bounce rates since no subsequent visits are registered (at least as part of the preview function).
The solution is to simply not output any analytics code when the X-Purpose header is set to "preview". In Interchange this is easily done if you have an include file for your analytics code, by wrapping the file with an [if] block such as this:
[tmp x_purpose][env HTTP_X_PURPOSE][/tmp] [if scratch x_purpose eq 'preview'] <!-- skip analytics for browser previews --> [else] (normal Google Analytics, Omniture SiteCatalyst, or other analytics code) [/else] [/if]
In Ruby on Rails you'd check request.env["HTTP_X_PURPOSE"].
In PHP you'd check $_SERVER["HTTP_X_PURPOSE"].
In Django you'd check request.META["HTTP_X_PURPOSE"] or the equivalent request.META.get("HTTP_X_PURPOSE") (from the HttpRequest class).
And so on.
I confirmed the analytics tracking code was omitted by waiting for Safari to make its preview request and inspecting the response with the Fiddler proxy, on Windows. The same can be done for Safari on Mac OS X with a suitable Mac OS X HTTP proxy.
DevCamps on different systems, including Plesk, CPanel and ISPConfig
In the last few months I've been active setting up DevCamps for several of our newer clients. DevCamps is an open source development environment system, that once setup, allows for easily starting up and tearing down a development environment for a specific site/
I've done many camps setups, and you tend to run into surprises from system to system, but what was most interesting and challenging about these latest installs was that they were to be done on systems running Plesk, CPanel, and ISPConfig. Some things that are different between a normal deployment and one on the above mentioned platforms are:
- On the Plesk system there was a secured Linux called 'Atomic Secured Linux' which includes the grsecurity module. One restriction of this module is (TPE) Trusted Path Execution which required the camp bin scripts to be owned by root and the bin directory could not be writable by other groups, otherwise they would fail to run.
- Permissions are a mixed bag, where typically we set all of the files to be owned by the site owner, in Plesk there are special groups such as psacln that the files need to be owned by.
- On the CPanel system we needed to move the admin images for Interchange to a different directory since CPanel includes Interchange and has aliases for /interchange/ and /interchange-5/ to point at a central location which we would not be using.
- On ISPConfig and Plesk the home directories of the sites are in different places, which required deploying the code in such places as /var/www/clients/client/user/domain.com or /var/www/vhosts/domain.com.
In the end we were able to get DevCamps to run properly on these various platforms both in development and production. If you are starting a new project or working on an existing project and could use a strong development environment, consider DevCamps.
XZ compression
XZ is a new free compression file format that is starting to be more widely used. The LZMA2 compression method it uses first became popular in the 7-Zip archive program, with an analogous Unix command-line version called 7z.
We used XZ for the first time in the Interchange project in the Interchange 5.7.3 packages. Compared to gzip and bzip2, the file sizes were as follows:
interchange-5.7.3.tar.gz 2.4M interchange-5.7.3.tar.bz2 2.1M interchange-5.7.3.tar.xz 1.7M
Getting that tighter compression comes at the cost of its runtime being about 4 times slower than bzip2, but a bonus is that it decompresses about 3 times faster than bzip2. The combination of significantly smaller file sizes and faster decompression made it a clear win for distributing software packages, leading to it being the format used for packages in Fedora 12.
It's also easy to use on Ubuntu 9.10, via the standard xz-utils package. When you install that with apt-get, aptitude, etc., you'll get a scary warning about it replacing lzma, a core package, but this is safe to do because xz-utils provides compatible replacement binaries /usr/bin/lzma and friends (lzcat, lzless, etc.). There is also built-in support in GNU tar with the new --xz aka -J options.
Dropped sessions when Ask.com Toolbar is installed
We've been dealing with an issue on a client's site where customers were reporting that they could not login and when they added items to their cart the cart would come up empty. This information pointed towards a problem with the customer's session being dropped, but we were unable to determine the common line across these customer's environments and came up empty handed. This was a case of being unable to reproduce a problem which made it nearly impossible to fix.This morning on the Interchange users list there was a post from Racke discussing a similiar issue. His customer had the Ask.com toolbar installed and Interchange's robot matching code was mistakenly matching the Ask.com toolbar as a search spider. The user agent of the browser with Ask.com installed appeared as so:
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; msn OptimizedIE8;ENUS; AskTB5.6)"
A quick look at the current robots.cfg that Steven Graham linked showed that 'AskTB' had been added to the NotRobotUA directive which instructs Interchange to not consider AskTB a search spider, thus allowing proper use of sessions on the site.
Updating the robots.cfg on our client's site allowed users with Ask.com to browse, login and checkout as expected. Those with Interchange sites who see reports of similiar issues should consider a false positive spider match a possibility and update their robots.cfg.
Performance optimization of icdevgroup.org
Some years ago Davor Ocelić redesigned icdevgroup.org, Interchange's home on the web. Since then, most of the attention paid to it has been on content such as news, documentation, release information, and so on. We haven't looked much at implementation or optimization details. Recently I decided to do just that.
Interchange optimizations
There is currently no separate logged-in user area of icdevgroup.org, so Interchange is primarily used here as a templating system and database interface. The automatic read/write of a server-side user session is thus unneeded overhead, as is periodic culling of the old sessions. So I turned off permanent sessions by making all visitors appear to be search bots. Adding to interchange.cfg:
RobotUA *
That would not work for most Interchange sites, which need a server-side session for storing mv_click action code, scratch variables, logged-in state, shopping cart, etc. But for a read-only content site, it works well.
By default, Interchange writes user page requests to a special tracking log as part of its UserTrack facility. It also outputs an X-Track HTTP response header with some information about the visit which can be used by a (to my knowledge) long defunct analytics package. Since we don't need either of those features, we can save a tiny bit of overhead. Adding to catalog.cfg:
UserTrack No
Very few Interchange sites have any need for UserTrack anymore, so this is commonly a safe optimization to make.
HTTP optimizations
Today I ran the excellent webpagetest.org test, and this was the icdevgroup.org test result. Even though icdevgroup.org is a fairly simple site without much bloat, two obvious areas for improvement stood out.
First, gzip/deflate compression of textual content should be enabled. That cuts down on bandwidth used and page delivery time by a significant amount, and with modern CPUs adds no appreciable extra CPU load on either the client or the server.
We're hosting icdevgroup.org on Debian GNU/Linux with Apache 2.2, which has a reasonable default configuration of mod_deflate that does this, so it's easy to enable:
a2enmod deflate
That sets up symbolic links in /etc/apache2/mods-enabled for deflate.load and deflate.conf to enable mod_deflate. (Use a2dismod to remove them if needed.)
I added two content types for CSS & JavaScript to the default in deflate.conf:
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript
That used to be riskier when very old browsers such as Netscape 3 and 4 claimed to support compressed CSS & JavaScript but actually didn't. But those browsers are long gone.
The next easy optimization is to enable proxy and browser caching of static content: images, CSS, and JavaScript files. By doing this we eliminate all HTTP requests for these files; the browser won't even check with the server to see if it has the current version of these files once it has loaded them into its cache, making subsequent use of those files blazingly fast.
There is, of course, a tradeoff to this. Once the browser has the file cached, you can't make it fetch a newer version unless you change the filename. So we'll set a cache lifetime of only one hour. That's long enough to easily cover most users' browsing sessions at a site like this, but short enough that if we need to publish a new version of one of these files, it will still propagate fairly quickly.
So I added to the Apache configuration file for this virtual host:
ExpiresActive On ExpiresByType image/gif "access plus 1 hour" ExpiresByType image/jpeg "access plus 1 hour" ExpiresByType image/png "access plus 1 hour" ExpiresByType text/css "access plus 1 hour" ExpiresByType application/x-javascript "access plus 1 hour" FileETag None Header unset ETag
This adds the HTTP response header "Cache-Control: max-age=3600" for those static files. I also have Apache remove the ETag header which is not needed given this caching and the Last-modified header.
There are cases where the above configuration would be too broad, for example, if you have:
- images that differ with the same filename, such as CAPTCHAs
- static files that vary based on logged-in state
- dynamically-generated CSS or JavaScript files with the same name
If the website is completely static, including the HTML, or identical for all users at the same time even though dynamically generated, we could also enable caching the HTML pages themselves. But in the case of icdevgroup.org, that would probably cause trouble with the Gitweb repository browser, live documentation searches, etc.
After those changes, we can see the results of a new webpagetest.org run and see that we reduced the bytes transferred, and the delivery time. It's especially dramatic to see how much faster subsequent page views of the Hall of Fame are, since it has many screenshot thumbnail images.
Optimizing a simple non-commerce site such as icdevgroup.org is easy and even fun. With caution and practicing on a non-production system, complex ecommerce sites can be optimized using the same techniques, with even more dramatic benefits.
Interchange news
Tomorrow we'll be having an Interchange community meeting on IRC. All Interchange users and any other interested parties are invited to participate.
Also, just recently, End Point's own David Christensen joined the Interchange Development Group and became a core committer. Congratulations, David, and keep up the good work!
LinuxTag 2009 day 1
Today was the first day of LinuxTag 2009. Representing the Interchange project are Stefan Hornburg of LinuXia Systems in Germany, Davor Ocelić of Spinlock Solutions in Croatia, and I, Jon Jensen, of End Point in the U.S.
Tuesday afternoon we set up the booth (here still underway):
That was a fairly quiet affair since many exhibitors showed up later that afternoon or early Wednesday morning. But it was nice to get it all done early. The setup involves the network and power wiring behind the scenes, hanging the signs, unloading the marketing materials, and getting all the equipment tested (and then put away again for the night). At night back in our apartment we made some updates to the slide presentation to include many more examples of some of the busy and interesting sites we have current data on that appear in the Interchange Hall of Fame.
We're sharing a booth with the YaCy distributed search project, and have had a few good discussions with their people.
Booth traffic was probably about the same this year as it was the first day last year -- a little slow. We talked with several people who were interested in hosted e-commerce solutions such as Interchange is.
In two cases, very interested visitors were not at all clear about how the open source partnership between project, individuals, and businesses works, and we were able to explain it. (Hopefully clearly enough that it still made sense after we were done talking!)
Specifically, one visitor represents a hosting company that wanted to pay to include Interchange in their hosting offerings. We are of course happy to take his money but have no set price to offer because, as he later succinctly put it, "Sie leben von Ihren Dienstleistungen." That is, "You live from your [custom] services." Exactly.
We explained the service model, his ability to download the software for free not just to evaluate, but to permanently use and resell as a hosted service to others. At the end it was clear it would've been simpler for him to hear it costs something like €500 or €5000 per year to be part of our hosting partner program. Yet that wouldn't have answered the question of what support we provide, how his programmers can contribute back to the Interchange community as they customize the software, etc. So the elaboration is necessary. And we explained that each of us three Interchange developers represents our three different consulting companies, and that's who you actually do business with, not "Interchange" per se.
The other visitor who didn't have a background with open source software wanted something similar, a fixed price to be allowed to deploy the software and customize it for his own consulting customers. A similar discussion was had. The bottom line is, the software's free, but work you hire us to do specifically for you is not. That's not too complicated once you get used to it!
In between talking with visitors, we talked about some work Stefan's been doing on the WellWell catalog, the need for new experimental Interchange branches which is much easier now with our new Git repository, and some custom work that's been done before that needs to be genericized and committed to mainline Interchange. We also dropped in on the Freenode #interchange IRC channel and worked a bit with Gert van der Spoel, René Hertell, David Christensen, and others.
Otherwise, I had time to attend one talk, Die Mathematik hinter RAID, by Michael Schwartzkopff. He worked through the math to show the probability of various kinds of failures when using RAID 1 and RAID 5, discussed RAID 6 and Sun's ZFS RAID-Z2. It was quite interesting and a good reminder that as hard disk capacity grows, what once seemed like incredibly small chances of failure (a one-bit read error, or a failure of a disk's mechanism) become both more likely and more catastrophic when they do occur.
So we're off to a good start, with three more days to go.
Using the new-style Google Analytics pageTracker functions in Interchange
For a while now there have been two different ways to setup the JavaScript calls to report traffic back to Google Analytics. The older method uses functions names that mention "urchin," while the newer method uses a function named "pageTracker". This post describes an approach for using the new method at a standard Interchange store.
You can see an example of the new method of reporting a page view here. Nothing Interchange-related is required for normal page tracking, but you may want to use a variable for the Google Account Number, of which more below.
If you have your Google Analytics account setup to treat the website as an E-commerce site, then you can also add the order tracking tags to your receipt page, so that it sends order data over to Google Analytics at the time of conversion. The order tracking tags can be viewed here. This gist shows the typical Interchange tags you might want to use to transmit the order specifics. Of course you might need to change the field used for the category for the products since not everyone uses the prod_group field from the products table to hold this information.
As you can see, both normal and the order-conversion scripts need to be modified to contain the individual Google Analytics account number for the website. I tend to set up an Interchange variable such as GOOGLE_ANALYTICS_ID in the variable.txt file or catalog.cfg.
Learn more about End Point's analytics expertise.
In Interchange, You Might Need to [try] [goto]. What's the [catch]?
Interchange provides tags that allow error trapping and handling within ITL--[try] and [catch]--that can be thought of as analogous to perl's eval {} followed by if ($@) {}. However, as I discovered the hard way, the analogy is not perfect.
I set up a block of ITL within [try] that had two major actions, with the 2nd depending on the success of the first. In particular, these two actions were a credit card authorization, followed by a capture of that auth as long as (a) the authorization succeeded, and (b) the merchant's internal rules for analyzing order content compared to AVS results "passed". (b) was necessary as a fraud-protection measure, tightening up the impact of AVS results based on the historic tendency of certain products to be targeted by crooks. In the event that the auth succeeded, but the tests from (b) failed, it is very important that the capture never be attempted because, to the gateway, the auth is entirely valid and the catpure attempt would succeed.
The code that assesses whether AVS passes is done in its own [calc]. From within the code, if the assessment does not pass, the code issues a die(), which in fact does trigger [try] to log the error that becomes accessible in [catch] via the $ERROR$ token, and thus does trigger [catch] to execute its body contents. In that way, the [try] did trap the error, and the error was handled in [catch], but of course that's not the end of the story or this post wouldn't exist.
After the code had been in production for some time, David Christensen brought to my attention that he noticed in development a test order attempt, where the order attempt failed, but both the auth and the capture succeeded. I was highly dubious of this claim and went over in great detail just what he had done. We narrowed down the condition that produced the problem to (b) above: a successful auth, but abort the order attempt anyway because a high-value product in the cart was coupled with a questionable AVS. When I went to the logs, I could see the result spelled out, but the result made no sense to my understanding:
### starting credit card processing ### Real-time full auth succeeded. ID=*** 0 Real-time full capture succeeded. ID=*** Error detected for order xxx in the credit card charge.
The 0 indicated the [calc] had failed (died), yet the capture later in the [try] was still executing. The only conclusion was that, unlike eval {}, when [try] trapped an error, it just kept right on processing the continuing ITL. [try] always went forward and processed all its ITL to the end, and whatever happened to be the last die() called within that batch of ITL would be the thing that [catch] caught and displayed.
To resolve the problem, I introduced [goto] into the block, which stops the instance of interpolate_html() running at the point of encounter and returns. Continuing to use the die() call to populate the error code from [try], immediately after the [calc] test block I called [goto] conditionally on whether the [calc] block, in fact, died. The [goto] call then terminated the instance of interpolate_html() that [try] had invoked on its body, which had the effect of stopping ITL execution at the point of the die().
This approach to emulating eval {}/if ($@) {} has the significant flaw of developers needing to know ahead of time exactly where in the [try] block such failures are expected. If such is unknowable, it leaves developers in the unenviable position of having to follow each tag call with a conditional [goto] that has to know when the previous tag "failed" (i.e., triggered a die() somewhere).
Subverting Subversion for Fun and Profit
One of our clients recently discovered a bug in a little-used but vital portion of the admin functionality of their site. (Stay with me here...) After traditional debugging techniques failed on the code in question, it was time to look to the VCS for identifying the regression.
We fortunately had the code for their site in version control, which is obviously a big win. Unfortunately (for me, at least), the repository was stored in Subversion, which means that my bag o' tricks was significantly diminished compared to my favorite VCS, git. After attempting to use 'svn log/svn diff -c' to help identify the culprit based on what I *thought* the issue might be, I realized that svn was just not up to the task.
Enter git-svn. Using git svn clone file://path/to/repository/trunk, I was able to acquire a git-ized version of the application's repository. For this client, we use DevCamps exclusively, so the entire application stack is stored in the local directory and run locally, including apache instance and postgres cluster. These pieces are necessarily unversioned, and are ignored in the repository setup. I was able to stop all camp services in the old camp directory (svn-based), rsync over all unversioned files to the new git repository (excluding the .svn metadata), replace the svn-based camp with the new git-svn based one, and fire up the camp services again. Started up immediately and worked like a charm. I now had git installed and working in what had previously only been svn-capable before.
Now that I had a git installation, I was able to pull one of my favorite tools from my toolbox when fighting regressions: git-bisect. In my previous svn contortions, I had located a previous revision several hundred commits back which did not exhibit the regression, so I was able to start the bisect with the following command: git bisect start bad good. In this case, bad was master and good was the revision I had found previously. Using git svn find-rev rnumber, I found the SHA1 commit for the good ref as git saw it.
From this point, I was able to quickly identify the commit which introduced the regression. In reviewing the diff, there was nothing that I would have expected to cause the issue at hand; the code did not touch any of the affected area of the admin. But git had never lied to me before. I compared the code currently in master with that introduced in the implicated commit and saw that most of it was still in place. I began selectively commenting out pieces of the code the commit introduced, and was able to enable/disable the bug with increasingly fine granularity. Finally, I was able to identify the single line which when removed caused the issue to evaporate. This was a line in an innocuous template which had a simple variable interpolation (inside an HTML comment, nonetheless); however, this line (which was in a file which was included with every document, added in the implicated commit) revealed a bug in the parser of the app-server which was causing the symptoms in the unrelated admin area.
It's certain that I would never have been able to find the source of this issue without git-bisect, as manual bisection with svn would have been too tedious to even consider. I am able to happily interact with the rest of the development team with git being my secret weapon; git svn dcommit enables me to push my commits upstream, and git svn fetch/git svn rebase enable me to pull in the upstream changes. I'll never need to tell my subversive secret (except, you know, on the company blog), and my own happiness and productivity has increased. Profit!!11 all around.
Interchange jobs caveat
I'd used Interchange's jobs feature to handle sending out email expirations and re-invites for a client. However I found out the hard way that scratch variables persisted between individual sub-jobs in the job set. I'd tested each of the two sub-jobs in isolation and had had no issues.This bit me because I'd assumed each job component was run in isolation and variables were initialized with sensible (aka empty) content. In my case it fortunately only affected the reporting of each piece of the job system, but definitely could have affected larger pieces of the system.
The lessons? 1) Always explicitly initialize your variables; you don't know the ultimate context they'll be run in. 2) Individual component testing is no substitute for testing a system as a whole; you can reveal bugs that would otherwise slip through.
Standardized image locations for external linkage
Here's an interesting thought: http://www.boingboing.net/2008/09/01/publishers-should-al.html
Nutshell summary: publishers should put cover images of books into a standard, predictable location (like http://www.acmebooks.com/covers/{ISBN}.jpg).
This could be extended for almost any e-commerce site where the product image might be useful for reviews, links, etc.
At very least, with Interchange action maps, a site could capture external references to such image requests for further study. (E.g., internally you might reference a product image as [image src="images/products/current{SKU}"], but externally as "/products/{SKU}.jpg"; the actionmap wouldn't be used for the site, but only for other sites linking to your images.)
RPM --nodeps really disables all dependency logic
I was surprised about something non-obvious in RPM's dependency handling for the second time today, the first time having been so many years ago that I had completely forgotten.
When testing out an RPM install without having all the required dependencies installed on the system, it's natural to do:
rpm -ivh $package --nodeps
The --nodeps option allows RPM to continue installing despite the fact that I'm missing a handful of packages that $package depends on. This shouldn't be done as a matter of course, but for a quick test, is fine. So far so good.
However, I found out by confusing experience that --nodeps not only allows otherwise fatal dependency errors to be skipped, but it also disables RPM's entire dependency tracking system!
I was working with 3 RPMs, a base interchange package and 2 ancillary interchange-* packages which depend on the base package, such as here:
interchange-5.6.0-1.x86_64.rpm interchange-standard-5.6.0-1.x86_64.rpm interchange-standard-demo-5.6.0-1.x86_64.rpm
Then when I installed them all at once:
rpm -ivh interchange-*.rpm --nodeps
I expected interchange to be installed first, followed by either of the interchange-standard-* packages that depend on it.
However, --nodeps disables RPM's tracking of those dependencies, causing them to be installed in what happened to be a pessimistic order that breaks many things. Since the interch user and group that the interchange package creates doesn't exist yet, files can't be owned by the correct user/group. And since the configuration file /etc/interchange.cfg doesn't exist yet, the interchange-standard-demo package can't register itself there.
I wasn't able to see this till I had Kiel join me in a shared screen and watch as I typed my install command. As I spoke aloud --nodeps to Kiel, I suddenly remembered my past experience with this and felt appropriately stupid.
What I really want is not to have no dependency checking at all, but rather something like a hypothetical --ignore-deps-errors option. Changing the behavior of --nodeps to do just that would probably be friendlier overall, but perhaps there's a reason for its current behavior ...
As an aside, I will note that the RPM specfile PreReq tag has been deprecated and is now a synonym for Requires.
Interchange 5.4.2 released
Today a new version on the Interchange 5.4 stable branch was released. This was primarily a bugfix release, as documented in the release notes summary.
Interchange 5.4.1 released
Interchange 5.4.1 was released today. This is a maintenance update of the web application server End Point has long supported.
There were a few important bugfixes:
There were also numerous other minor bugfixes in core code, tags, the admin, and the Standard demo.
We invite you to learn more about Interchange if you're not familiar with it.
Interchange 5.4 Released
At the end of December 2005, a new major version of Interchange was released, making widely available the improvements developed over the previous year and a half.
While many of the hundreds of important changes are small and incremental, Interchange 5.4 offers a number of larger improvements as well:
- Improved pre-fork server model supports higher traffic.
- Extensible architecture improvements allow more customization (Feature, AccumulateCode, TagRepository, DispatchRoutines).
- Shopping cart triggers have been added, for easier control over complex shopping cart behaviors.
- Multiple "discount spaces" may be defined, for complex discounting schemes.
- The "permanent more" facility allows shared pageable searches, for reduced database load and paging disk space.
- The email interception feature reroutes outgoing email to one or more developer addresses, stopping email from accidentally going to real users during testing.
- Quicker development of email functions using HTML parts or attached files.
- A new demo application, called "Standard", was added.
- Access to loop data in embedded Perl is now easier with the new $Row object.
- User-defined subroutines can be accessed more ways with the new $Sub object.
- More payment gateways are supported, including an interface to CPAN's Business::OnlinePayment.
- More languages are supported in the admin area.
- ... and many other feature enhancements and bugfixes.
Ethan Rowe and Jon Jensen, two End Point engineers and members of the Interchange Development Group, added several of these new features based on work done earlier for our clients. We value highly the whole Interchange team's commitment to stability and reliability in the code, and cooperation and ongoing improvement together. In particular we appreciate the efforts of Mike Heins, Stefan Hornburg, and Davor Ocelic, whose regular contributions make Interchange's progress impressive. And Interchange would be weaker without the valuable work of Kevin Walsh, Ton Verhagen, Jonathan Clark, Dan Browning, Paul Vinciguerra, Ed LaFrance, and others.
We look forward to seeing this latest and greatest version of Interchange being used by the wider Interchange community.


