oscommerce suport , oscommerce help , oscommerce programers

osCommerce Support
Call Now 1.800.961.7427

 
 
osCommerce Clothing Store

osCommerce Auto Parts Store
OsCommerce Store Paypal
 
   
osCommerce Support this is the osCommerce.com Forum * we are not osCommerce.com
Help - Search - Members - Calendar
Full Version: A Store Speed Optimization in Progress
osCommerce Community Support Forums > osCommerce Online Merchant v2.x > Tips and Tricks
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
Chemo
Hello,

I have been working on optimizing the MS2 codebae for several months and am going to do a test site from scratch so everyone can follow along.

This is my development server and I'm going to keep the admin area open for everyone. Don't mess this up with malicious stuff or I'll lock it down.

On the bottom of every page there is a debug output with parse time, query count, and the actual queries used to construct the page. I'll be using this info to optimize the queries.

Can anyone take a look and see any redundant queries? It's a loaded question since I've already been down this block and know where there is opportunity to streamline. Let's see if anyone wants to follow along as we get this down to 10 queries per page and sub .1 second render times (read: bad ass cart performance).

The only requirement to the optimizations are a net increase in performance and NO LOSS OF FUNCTIONALITY.

Vanilla Install Stats:
Parse Time: .328 seconds
Queries - 92

Disable Category Counts:
Parse Time: .259 seconds
Queries: 52

So, save 40 queries right off the bat for disabling category counts.

More optimizations tomorrow...whe I start to dissect the code and get this puppy screaming.
Chemo
So now I have removed the banner code and the stats have dropped:

Page Render Time: .221 seconds
Queries: 47

Tomorrow I am going to the sources of these apparently redundant queries:

CODE
           [12] => select products_id, products_image, products_tax_class_id, products_price from products where products_status = '1' order by products_date_added desc limit 10
           [13] => select products_name from products_description where products_id = '21' and language_id = '1'
           [14] => select specials_new_products_price from specials where products_id = '21' and status
           [15] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [16] => select p.products_id, p.products_image, p.products_tax_class_id, if(s.status, s.specials_new_products_price, p.products_price) as products_price from products p left join specials s on p.products_id = s.products_id where products_status = '1' order by p.products_date_added desc limit 9
           [17] => select products_name from products_description where products_id = '7' and language_id = '1'
           [18] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [19] => select products_name from products_description where products_id = '21' and language_id = '1'
           [20] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [21] => select products_name from products_description where products_id = '10' and language_id = '1'
           [22] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [23] => select products_name from products_description where products_id = '24' and language_id = '1'
           [24] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [25] => select products_name from products_description where products_id = '13' and language_id = '1'
           [26] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [27] => select products_name from products_description where products_id = '27' and language_id = '1'
           [28] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [29] => select products_name from products_description where products_id = '2' and language_id = '1'
           [30] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [31] => select products_name from products_description where products_id = '16' and language_id = '1'
           [32] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [33] => select products_name from products_description where products_id = '5' and language_id = '1'
           [34] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [35] => select p.products_id, pd.products_name, products_date_available as date_expected from products p, products_description pd where to_days(products_date_available) >= to_days(now()) and p.products_id = pd.products_id and pd.language_id = '1' order by date_expected desc limit 10
           [36] => select distinct p.products_id, pd.products_name from products p, products_description pd where p.products_status = '1' and p.products_ordered > 0 and p.products_id = pd.products_id and pd.language_id = '1' order by p.products_ordered desc, pd.products_name limit 10
           [37] => select p.products_id, pd.products_name, p.products_price, p.products_tax_class_id, p.products_image, s.specials_new_products_price from products p, products_description pd, specials s where p.products_status = '1' and p.products_id = s.products_id and pd.products_id = s.products_id and pd.language_id = '1' and s.status = '1' order by s.specials_date_added desc limit 10
           [38] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority
           [39] => select sum(tax_rate) as tax_rate from tax_rates tr left join zones_to_geo_zones za on (tr.tax_zone_id = za.geo_zone_id) left join geo_zones tz on (tz.geo_zone_id = tr.tax_zone_id) where (za.zone_country_id is null or za.zone_country_id = '0' or za.zone_country_id = '223') and (za.zone_id is null or za.zone_id = '0' or za.zone_id = '18') and tr.tax_class_id = '1' group by tr.tax_priority

Can anyone guess where they are located? smile.gif
Chemo
First, I removed the banner (none of my sites or client sites use banners). This brought the page query count down to:

Page Render Time: .221 seconds
Queries: 47

Second, I optimized the tep_get_tax() method and also tep_get_tax_description() method. Previously, I approached the same problem and used a session based solution. In retrospect, this was a sloppy -- but highly effective -- way of decreasing the query count since each price displayed on the shopping cart used 1-2 additional queries just to get the tax rate (even if you chose to disable display with tax).

The code for MS3 approaches this fundamental problem of excessive queries with a nice tax class. Basically, I used the next tax class for MS3 and adapted it for MS2.

Page Parse Time: .179 seconds
Queries: 36

Now, I'm going to look at this set of queries:
CODE
           [17] => select products_name from products_description where products_id = '7' and language_id = '1'
           [18] => select products_name from products_description where products_id = '21' and language_id = '1'
           [19] => select products_name from products_description where products_id = '10' and language_id = '1'
           [20] => select products_name from products_description where products_id = '24' and language_id = '1'
           [21] => select products_name from products_description where products_id = '13' and language_id = '1'
           [22] => select products_name from products_description where products_id = '27' and language_id = '1'
           [23] => select products_name from products_description where products_id = '2' and language_id = '1'
           [24] => select products_name from products_description where products_id = '16' and language_id = '1'
           [25] => select products_name from products_description where products_id = '5' and language_id = '1'

They are all the same...basically querying for the product name. This is a sign to me that a query somewhere can be optimized to use a JOIN and not a separate method to query.

Where is it though? Can anyone find it before I fix it?
bruyndoncx
something like this in product_listing.php ?
$listing['products_name'] = tep_get_products_name($listing['products_id']);

the actual query being defined in index.php and advanced_search_result.php
bruyndoncx
can you share the tax class modification you made for MS2 ?
Chemo
Implement MS3 Tax Class for MS2
Chemo
For those following along the query count is now down to 27.

The vanilla install was 92 queries and we have gotten that down to 27 with only a few modifications. This is a 70% reduction with only 3 minor file changes and a configuration setting.

There are other optimization techniques I'll use in the upcoming days and also add a few popular contributions. Similiarly, I'll optimize the queries for each added contribution.
wizardsandwars
Next thing I would do is eliminate the 'Page Requests since'. This is a useless stat, as it counts each time anyone requests a page form the site, including any and all search engine spiders.

Also, I think there are some very good indexes we could add to the DB to help the query time a little. Probalby won't make a huge difference with a stock store, but once a couple hundred products or more are added, this will help tremendously.

Other suggestions

- Remove tables in favor of css placement
- Enable gzip compression
- install the configuration case contribution
RavenWulf
QUOTE (Chemo @ Oct 31 2004, 02:30 PM)
First, I removed the banner (none of my sites or client sites use banners).  This brought the page query count down to:

Page Render Time: .221 seconds
Queries: 47

Second, I optimized the tep_get_tax() method and also tep_get_tax_description() method.  Previously, I approached the same problem and used a session based solution.  In retrospect, this was a sloppy -- but highly effective -- way of decreasing the query count since each price displayed on the shopping cart used 1-2 additional queries just to get the tax rate (even if you chose to disable display with tax).

The code for MS3 approaches this fundamental problem of excessive queries with a nice tax class.  Basically, I used the next tax class for MS3 and adapted it for MS2.

Page Parse Time: .179 seconds
Queries: 36

Now, I'm going to look at this set of queries:
CODE
           [17] => select products_name from products_description where products_id = '7' and language_id = '1'
           [18] => select products_name from products_description where products_id = '21' and language_id = '1'
           [19] => select products_name from products_description where products_id = '10' and language_id = '1'
           [20] => select products_name from products_description where products_id = '24' and language_id = '1'
           [21] => select products_name from products_description where products_id = '13' and language_id = '1'
           [22] => select products_name from products_description where products_id = '27' and language_id = '1'
           [23] => select products_name from products_description where products_id = '2' and language_id = '1'
           [24] => select products_name from products_description where products_id = '16' and language_id = '1'
           [25] => select products_name from products_description where products_id = '5' and language_id = '1'

They are all the same...basically querying for the product name.  This is a sign to me that a query somewhere can be optimized to use a JOIN and not a separate method to query.

Where is it though?  Can anyone find it before I fix it?
*



Chemo

umm I am new to php and such and was wondering where and how you removed the banner code. was this done in the footer?

Could you please explain?

Thank you
RavenWulf
Chemo
Sure...

In application_top you'll find code that looks like the following around line 448 or so:
CODE
// auto activate and expire banners
require(DIR_WS_FUNCTIONS . 'banner.php');
tep_activate_banners();
tep_expire_banners();


Replace it with this code:
CODE
// auto activate and expire banners
#  require(DIR_WS_FUNCTIONS . 'banner.php');
#  tep_activate_banners();
#  tep_expire_banners();


Notice, I used the old style # comment tag. Yes, hard habit are hard to break (for those that know wha I mean).

Next, in footer.php you'll find code that looks like this:
CODE
<?php
 if ($banner = tep_banner_exists('dynamic', '468x50')) {
?>
<br>
<table border="0" width="100%" cellspacing="0" cellpadding="0">
 <tr>
   <td align="center"><?php #echo tep_display_banner('static', $banner); ?></td>
 </tr>
</table>
<?php
 }
?>

Change it to this:
CODE
<?php
 #if ($banner = tep_banner_exists('dynamic', '468x50')) {
?>
<!-- remove banner code
<br>
<table border="0" width="100%" cellspacing="0" cellpadding="0">
 <tr>
   <td align="center"><?php #echo tep_display_banner('static', $banner); ?></td>
 </tr>
</table>
//-->
<?php
 #}
?>

Notice, I used the classic HTML comment tag and also the old style comment # for the PHP.

This change netted us 5 reduced queries per page.

As a side note, have you thought about installing a debug code on your site to help with optimizing? It is the same kind of output on the bottom of the dev site.
996
What kind of code would i use to show this: Queries: 52

thanks. Mod seems to be working great but would love to see how many queries it cut down
Dragonmom
This is so interesting! I'll be following this development.
many of my visitors arrive via dialup, so load time is important to me.
I can only add contribs- I don't understand how to modify code. I removed the date and visitor counter first thing, is there a queriy that I need to remove also? I don't see anything in application_top.php
Jerah
Chemo, you sir are a legend. smile.gif

I've been wading through the other optimisation thread looking for ways of cutting queries and upping speed for my (heavily customised) site. It's great to find a thread that starts off right at the basics - the base code.

Keep up the good work!
boxtel
QUOTE (Chemo @ Oct 31 2004, 02:30 PM)
First, I removed the banner (none of my sites or client sites use banners).  This brought the page query count down to:

Page Render Time: .221 seconds
Queries: 47

Second, I optimized the tep_get_tax() method and also tep_get_tax_description() method.  Previously, I approached the same problem and used a session based solution.  In retrospect, this was a sloppy -- but highly effective -- way of decreasing the query count since each price displayed on the shopping cart used 1-2 additional queries just to get the tax rate (even if you chose to disable display with tax).

The code for MS3 approaches this fundamental problem of excessive queries with a nice tax class.  Basically, I used the next tax class for MS3 and adapted it for MS2.

Page Parse Time: .179 seconds
Queries: 36

Now, I'm going to look at this set of queries:
CODE
           [17] => select products_name from products_description where products_id = '7' and language_id = '1'
           [18] => select products_name from products_description where products_id = '21' and language_id = '1'
           [19] => select products_name from products_description where products_id = '10' and language_id = '1'
           [20] => select products_name from products_description where products_id = '24' and language_id = '1'
           [21] => select products_name from products_description where products_id = '13' and language_id = '1'
           [22] => select products_name from products_description where products_id = '27' and language_id = '1'
           [23] => select products_name from products_description where products_id = '2' and language_id = '1'
           [24] => select products_name from products_description where products_id = '16' and language_id = '1'
           [25] => select products_name from products_description where products_id = '5' and language_id = '1'

They are all the same...basically querying for the product name.  This is a sign to me that a query somewhere can be optimized to use a JOIN and not a separate method to query.

Where is it though?  Can anyone find it before I fix it?
*



I believe it is in the new_products module where function tep_get_products_name($new_products['products_id']); is called in a loop. Makes no sense since the product name is already retrieved in the products query.
I commented that function out without problems.

PS. great contribution "page_cache", improves a lot. Maybe one could filter out the possible session_id's from the parameters.

Amanda.
Chemo
QUOTE (996 @ Oct 31 2004, 07:07 PM)
What kind of code would i use to show this: Queries: 52

thanks.  Mod seems to be working great but would love to see how many queries it cut down
*

I'll put it into a contribution and post it...should be a quick and install.

QUOTE (Dragonmom @ Oct 31 2004, 09:02 PM)
This is so interesting! I'll be following this development.
many of my visitors arrive via dialup, so load time is important to me.
I can only add contribs- I don't understand how to modify code. I removed the date and visitor counter first thing, is there a queriy that I need to remove also? I don't see anything in application_top.php
*

There are several areas that can be optimized for your particular site! Some want the banner code whereas others don't. Some love the hit counter at the bottom whereas others don't. See the trend? The stock osCommerce installation has them all in there so someone does not have to add but if they don't need them it just kills the query count.

Tell me what you want disabled and I'll give you the code to take it out...

QUOTE (Jerah @ Oct 31 2004, 09:21 PM)
Chemo, you sir are a legend. smile.gif

I've been wading through the other optimisation thread looking for ways of cutting queries and upping speed for my (heavily customised) site. It's great to find a thread that starts off right at the basics - the base code.

Keep up the good work!
*

Thank you very much smile.gif Wait until you see code that I haven't released yet that will bring the count down to 5 queries and sub .1 second page load times!

QUOTE (boxtel @ Nov 1 2004, 01:23 AM)
I believe it is in the new_products module where function tep_get_products_name($new_products['products_id']); is called in a loop. Makes no sense since the product name is already retrieved in the products query.
I commented that function out without problems.

PS. great contribution "page_cache", improves a lot. Maybe one could filter out the possible session_id's from the parameters.

Amanda.
*

YOU ARE CORRECT! Use a LEFT JOIN to pull the name as well and it eliminates a number of queries!
Chemo
For those that requested the query output and page parse times I have added the contribution and can be downloaded here: Output Queries Debug

Now, let's get it installed so you can see the effectiveness of the optimizations smile.gif
Chemo
BTW, if you use the query and parse time debug contribution in combination with the page cache you'll have to add it as a global variable in the cache_this_page() method of the includes/classes/page_cache.php file.

For instance, the cache_this_page() method should look like this:
CODE
/*###############################################################################
###

cache_this_page() method
    - no arguments
    - no return

################################################################################
###*/
    function cache_this_page () {
 global $debug;  
 if ($this->is_guest && ENABLE_PAGE_CACHE =='true') {
     $this->debug_output['is_guest_check'] = 'customer_id not set - cache_this_page()';
     if ( $this->cache_file_exists && !$this->cache_file_is_expired ) {
   $this->debug_output['file_exists_and_is_not_expired'] = 'file exists and is not expired';
   echo str_replace("<%CART_CACHE%>", $this->customer_cart, file_get_contents($this->cache_file) );    
   $this->debug();
   include_once (DIR_WS_INCLUDES . 'application_bottom.php');
   exit;
     } # END file exists and is not expired      
     if ( !$this->cache_file_exists || $this->cache_file_is_expired ) {
   $this->debug_output['no_file_or_expired'] = 'file does not exist or is expired';
   $this->write_cache_file = true;
   $this->output_ob = true;
   ob_start();
   $this->debug_output['ob_started'] = 'ob started @ '. time();
     } # END is file does not exist or is expired      
 } # END if $this->is_guest  
    } # END cache_this_page method

NOTICE: all I did was add the $debug as global at the top!
Chemo
I have added my page cache contribution and here are the stats:

Page Parse Time: .082 seconds
Queries: 8

As of now, I only have the index.php file cached so product_info.php and others are still fully generated. However, you can see the benefit of having the page cached.

In addition, I have documented each change to the code which can be viewed at the bottom of the page. The info contained is: name of change, parse time, query count, and reference (download link, reference link, etc.) so that others can go directly to the contribution.

Some contributions have decreased the query count considerable and others not so much. However, each is important for their own reason.

Next, I'll start working on the category pages and reviews.

My goal here is to get each page of the store down to about 10 queries (no more than) and sub .1 rended times.

Anyone interested in following along?
RavenWulf
QUOTE (Chemo @ Nov 1 2004, 01:07 PM)
I'll put it into a contribution and post it...should be a quick and install.
There are several areas that can be optimized for your particular site!  Some want the banner code whereas others don't.  Some love the hit counter at the bottom whereas others don't.  See the trend?  The stock osCommerce installation has them all in there so someone does not have to add but if they don't need them it just kills the query count.

Tell me what you want disabled and I'll give you the code to take it out...
Thank you very much smile.gif  Wait until you see code that I haven't released yet that will bring the count down to 5 queries and sub .1 second page load times!
YOU ARE CORRECT!  Use a LEFT JOIN to pull the name as well and it eliminates a number of queries!
*



Chemo, Thank you for all of this! I was looking into how to speed things up and all since I have a very modded store and want it as speedy as it can be. On that, I am still very new at all of this (great at adding contribs and getting things working, but all the rest is still hard work), and was wondering if you could give detailed instructions of what you have done, where, and how? such as the join function, how do I use it, where, and what does it do? (sorry I like to know more about things than just do this and it will work, I like to know why LOL)

Thank you again for all of this, I eagerly await more!

RavenWulf
bruyndoncx
I would but I have 2 excuses
- being a developer, so it wouldn't prove 3.
- got enable/disable categories installed

But you can have my code when you release the contribution smile.gif
RavenWulf
QUOTE (bruyndoncx @ Nov 1 2004, 08:44 PM)
I would but I have 2 excuses
- being a developer, so it wouldn't prove 3.
- got enable/disable categories installed

But you can have my code when you release the contribution smile.gif
*



what does have enable/disable categories contribution have to do with it? I ask because I also have this installed.

RW
Chemo
QUOTE (bruyndoncx @ Nov 1 2004, 03:44 PM)
I would but I have 2 excuses
- being a developer, so it wouldn't prove 3.
- got enable/disable categories installed

But you can have my code when you release the contribution smile.gif
*

If you're a developer you'll no doubt recognize some of the basic things I'm doing here. This about it like this: if you can save 84 queries per page how many would that save in a year? Assuming 1,000 visitors per day (spiders included) and 3 pages viewed per visit it would save 9,198,000 queries per year.

If you're like me and have a dedicated server with several (50+) osCommerce sites installed that would mean almost half a billion queries!! I'm doing this purely to save my MySQL server...it's cheaper than getting a dedicated MySQL server.

BTW, even if you have enable/disable categories installed (which I do on several sites) the improvements here will give the same benefits and are completely compatible.

QUOTE (RavenWulf @ Nov 1 2004, 03:56 PM)
what does have enable/disable categories contribution have to do with it? I ask because I also have this installed.

RW
*

The only way that it will be affected is if you install the page cache. This is because the page is cached and will be served until it expires. You can set the timelife in the admin control panel and can be anywhere from 1 minute to 1 year.
RavenWulf
QUOTE (Chemo @ Nov 1 2004, 09:12 PM)
If you're a developer you'll no doubt recognize some of the basic things I'm doing here.  This about it like this: if you can save 84 queries per page how many would that save in a year?  Assuming 1,000 visitors per day (spiders included) and 3 pages viewed per visit it would save 9,198,000 queries per year. 

If you're like me and have a dedicated server with several (50+) osCommerce sites installed that would mean almost half a billion queries!!  I'm doing this purely to save my MySQL server...it's cheaper than getting a dedicated MySQL server.

BTW, even if you have enable/disable categories installed (which I do on several sites) the improvements here will give the same benefits and are completely compatible. 
The only way that it will be affected is if you install the page cache.  This is because the page is cached and will be served until it expires.  You can set the timelife in the admin control panel and can be anywhere from 1 minute to 1 year.
*



so even if I have the enable/disable installed all I would need to do is anytime I change what catagories are there "flush" the cache and recreate it? Is there an admin function to page cache to do this? or is it simple set it to expire in a min, wait, then reenable it for however long I want?

Thank
RW
Chemo
QUOTE (RavenWulf @ Nov 1 2004, 04:42 PM)
so even if I have the enable/disable installed all I would need to do is anytime I change what catagories are there "flush" the cache and recreate it?  Is there an admin function to page cache to do this? or is it simple set it to expire in a min, wait, then reenable it for however long I want?

Thank
RW
*

There is an auto cache flush feature built in....go to the testing server and then to the admin directory. I don't have it password protected as I have faith in the osC community to not do malicious things.

Under the Configuration setting group find the Page Cache Settings. Next, find the delete cache files option. Set it to "true" and update. On the very first page call after that the files will be deleted and the value in the database set back to false.

So, it is an automatic function to delete the cache files.

Go try it out smile.gif
Fallout
Hello, I also want to install this nice Contribution.


However, have a problem with Install?

STEP 3 (catalog,includes,funktions, general.php)

I found the Code not in my File.
CODE
function tep_db_query($query, $link = 'db_link') {
     global $$link;
     
     if (defined('STORE_DB_TRANSACTIONS') && (STORE_DB_TRANSACTIONS == 'true')) {
     error_log('QUERY ' . $query . "\n", 3, STORE_PAGE_PARSE_TIME_LOG);
     }
     
     $result = mysql_query($query, $$link) or tep_db_error($query, mysql_errno(), mysql_error());
     
     if (defined('STORE_DB_TRANSACTIONS') && (STORE_DB_TRANSACTIONS == 'true')) {
     $result_error = mysql_error();
     error_log('RESULT ' . $result . ' ' . $result_error . "\n", 3, STORE_PAGE_PARSE_TIME_LOG);
     }
     
     return $result;
     }


Can you help me with the Installation?

LG
wizardsandwars
Just wanted to chime in here. I've had a long running site up (over 2 years now), and I thought I had optimized it about as far as I could.

The only thing *new* to me in this thread thus far was the 'page cache'. I gotta admit, I'm impressed. My site went form loading in about 38 seconds on a 56k (graphics heavy) to loading in about 19 seconds.

That's a pretty hefty imporvement, and I like how you can control how long the case page last for. Terrific improvements. Thanks for the heads up.
booster
QUOTE (Chemo @ Oct 31 2004, 04:38 PM)


Excellent - thanks a lot biggrin.gif

I'm looking forward to this thread developing to the same high standard.
(I'm very new to osC so can't contribute much.)

Dave.
Chemo
QUOTE (Fallout @ Nov 1 2004, 07:17 PM)
Hello, I also want to install this nice Contribution.
However, have a problem with Install?

STEP 3 (catalog,includes,funktions, general.php)

I found the Code not in my File.
CODE
function tep_db_query($query, $link = 'db_link') {
< SNIP >


Can you help me with the Installation?

LG
*

That function is actually located in includes/functions/database.php around line37 or so.

I apologize and will correct the install file now.

Download the latest query debug here: Debug Query Output

QUOTE (wizardsandwars @ Nov 1 2004, 08:47 PM)
Just wanted to chime in here. I've had a long running site up (over 2 years now), and I thought I had optimized it about as far as I could.

The only thing *new* to me in this thread thus far was the 'page cache'. I gotta admit, I'm impressed.  My site went form loading in about 38 seconds on a 56k (graphics heavy) to loading in about 19 seconds.

That's a pretty hefty imporvement, and I like how you can control how long the case page last for. Terrific improvements. Thanks for the heads up.
*

You've been around the block so know there is always a downside to everything. The page cache also keep the traditionally dynamic portions cached (like specials, featured products, best sellers, etc.). The only thing left to generate on each page request is the customer or guests cart. Also, once they register or log in the cache system turns itself off (for safety). It's only active for guests not logged in.

I figure if they log in they should get the full work of the server smile.gif

QUOTE (booster @ Nov 1 2004, 09:48 PM)
Excellent - thanks a lot  biggrin.gif

I'm looking forward to this thread developing to the same high standard.
(I'm very new to osC so can't contribute much.)

Dave.
*

Stay tuned...there's more to come. The next steps are:
1. Add popular contributions (discount coupons, featured, specials, etc)
2. Optimize again
3. Search Engine Optmization ala Chemo style

As a side note to #3: it's going to be like the "archive" feature of vBulletin discussion forum software. It will go beyond all_products and take SEO / sitemaps to another level for osCommerce. Enough of me blowing my own horn...back to coding smile.gif
Chemo
QUOTE (booster @ Nov 1 2004, 09:48 PM)
Excellent - thanks a lot  biggrin.gif

I'm looking forward to this thread developing to the same high standard.
(I'm very new to osC so can't contribute much.)

Dave.
*

Almost forgot...you can also define a longer period on a per page basis from the admin control panel setting.

For instance, the default code at the top of each page looks like this:
CODE
require(DIR_WS_CLASSES . 'page_cache.php');
$page_cache = new page_cache($cart_cache);
$page_cache->cache_this_page();


Change it to something like this:
CODE
require(DIR_WS_CLASSES . 'page_cache.php');
$page_cache = new page_cache($cart_cache, 1440, false);
$page_cache->cache_this_page();


Here's the function and arguments
cache_this_page($cart_info, $cachelifetime=5, $debugmode=false)

So, for pages like all_products, printable_catalog, etc. where there is not much traffic other than search engine spiders I can set the cache lifetime much higher (or, lower).

I try to make the code as versatile as I can smile.gif
RavenWulf
QUOTE (Chemo @ Nov 2 2004, 03:19 AM)
Almost forgot...you can also define a longer period on a per page basis from the admin control panel setting.

For instance,  the default code at the top of each page looks like this:
CODE
require(DIR_WS_CLASSES . 'page_cache.php');
$page_cache = new page_cache($cart_cache);
$page_cache->cache_this_page();


Change it to something like this:
CODE
require(DIR_WS_CLASSES . 'page_cache.php');
$page_cache = new page_cache($cart_cache, 1440, false);
$page_cache->cache_this_page();


Here's the function and arguments
cache_this_page($cart_info, $cachelifetime=5, $debugmode=false)

So, for pages like all_products, printable_catalog, etc. where there is not much traffic other than search engine spiders I can set the cache lifetime much higher (or, lower).

I try to make the code as versatile as I can smile.gif
*



Hey Chemo,

I am installing the m3 tax class for ms2 contribution, but hit a snag. in step3 second part is says replace tep_get_tax_description() with this code, but I don't have a function for teg_get_tax_description. I am using 2.2 ms 2 (latest download from oscommerce site)..any ideas?

Thanks!
RW
Chemo
QUOTE (RavenWulf @ Nov 2 2004, 12:17 PM)
Hey Chemo,

I am installing the m3 tax class for ms2 contribution, but hit a snag. in step3 second part is says replace tep_get_tax_description() with this code, but I don't have a function for teg_get_tax_description.  I am using 2.2 ms 2 (latest download from oscommerce site)..any ideas?

Thanks!
RW
*

The method tep_get_tax_rate() is located in includes/functions/general.php and was a typo. I've corrected it in the latest install (I think).

Check on my dev server for lastest files and pre-releases: osCommerce contritutions on my server

Also, you can go right to the install file here: MS3 Tax Class for MS2
wizardsandwars
Before you move on, I'd like to ask a quick question about the page cashe.

If you have this enabled, won't all of your guests have the same exact session ID? That seems to be the case.
Chemo
That may be the case if your store has the session id's appended to each URL. However, most do not and only use them in the URL when going from / to SSL / NONSSL.

It hasn't been problem on my sites but that is something to report and should be easily fixed.
wizardsandwars
Not sure what you mean.

The sid will be attached to the URL if cookies are not enabled in the customer's browser, and 'force cookie' use is not set to true.

Since IE is now being distributed with cookies NOT enabled, I would think that this would actually represent the majority of users very soon.

And the session is not only used when going form SSL to nonSSL, but also what you have in your cart. Not to mention that if one person purchases using that session, and enters their details to create an account, and then another person comes along and uses that session, they will have access to all of the first person's account details.

That could be a very serious issue.
wizardsandwars
Another point recently brought to my attention is that if a search engine parses that cashed page, they will also get that very same session id. And the last thing you want to do is have search engine links with session ids in them.
RavenWulf
QUOTE (Chemo @ Nov 2 2004, 05:25 PM)
The method tep_get_tax_rate() is located in includes/functions/general.php and was a typo.  I've corrected it in the latest install (I think).

Check on my dev server for lastest files and pre-releases: osCommerce contritutions on my server

Also, you can go right to the install file here: MS3 Tax Class for MS2
*



Heya, thanks for the quick reply.
I went to your server and dl the v1.1 for the m3 tax class for oscommerce.. however in the install instructions it still says:
STEP 3 - Edit includes/functions/general.php
REPLACE tep_get_tax_rate() it with this code:
function tep_get_tax_rate($class_id, $country_id = -1, $zone_id = -1) {
global $customer_zone_id, $customer_country_id, $osC_Tax;
return $osC_Tax->getTaxRate($class_id, $country_id, $zone_id);
}

REPLACE tep_get_tax_description() it with this code:
function tep_get_tax_description($class_id, $country_id, $zone_id) {
global $osC_Tax;
return $osC_Tax->getTaxRateDescription($class_id, $country_id, $zone_id);
}

I have done the 1st one (tep_get_tax_rate), but I don't have tep_get_tax_description in my general.php file in the catalog/includes/functions folder.. any ideas? below is a copy of my general.php file:

<?php
/*
$Id: general.php,v 1.160 2003/07/12 08:32:47 hpdl Exp $
++++ modified for USPS Methods 2.5 08/02/03 by Brad Waite and Fritz Clapp ++++
osCommerce, Open Source E-Commerce Solutions
http://www.oscommerce.com

Copyright © 2003 osCommerce

Released under the GNU General Public License
*/


////
// Redirect to another page or site
function tep_redirect($url) {
global $logger;

header('Location: ' . $url);

if (STORE_PAGE_PARSE_TIME == 'true') {
if (!is_object($logger)) $logger = new logger;
$logger->timer_stop();
}

exit;
}

////
// Parse the data used in the html tags to ensure the tags will not break
function tep_parse_input_field_data($data, $parse) {
return strtr(trim($data), $parse);
}

function tep_output_string($string, $translate = false, $protected = false) {
if ($protected == true) {
return htmlspecialchars($string);
} else {
if ($translate == false) {
return tep_parse_input_field_data($string, array('"' => '&quot;'));
} else {
return tep_parse_input_field_data($string, $translate);
}
}
}

function tep_output_string_protected($string) {
return tep_output_string($string, false, true);
}

function tep_sanitize_string($string) {
$string = ereg_replace(' +', ' ', $string);

return preg_replace("/[<>]/", '_', $string);
}

function tep_customers_name($customers_id) {
$customers = tep_db_query("select customers_firstname, customers_lastname from " . TABLE_CUSTOMERS . " where customers_id = '" . (int)$customers_id . "'");
$customers_values = tep_db_fetch_array($customers);

return $customers_values['customers_firstname'] . ' ' . $customers_values['customers_lastname'];
}

function tep_get_path($current_category_id = '') {
global $cPath_array;

if ($current_category_id == '') {
$cPath_new = implode('_', $cPath_array);
} else {
if (sizeof($cPath_array) == 0) {
$cPath_new = $current_category_id;
} else {
$cPath_new = '';
$last_category_query = tep_db_query("select parent_id from " . TABLE_CATEGORIES . " where categories_id = '" . (int)$cPath_array[(sizeof($cPath_array)-1)] . "'");
$last_category = tep_db_fetch_array($last_category_query);

$current_category_query = tep_db_query("select parent_id from " . TABLE_CATEGORIES . " where categories_id = '" . (int)$current_category_id . "'");
$current_category = tep_db_fetch_array($current_category_query);

if ($last_category['parent_id'] == $current_category['parent_id']) {
for ($i = 0, $n = sizeof($cPath_array) - 1; $i < $n; $i++) {
$cPath_new .= '_' . $cPath_array[$i];
}
} else {
for ($i = 0, $n = sizeof($cPath_array); $i < $n; $i++) {
$cPath_new .= '_' . $cPath_array[$i];
}
}

$cPath_new .= '_' . $current_category_id;

if (substr($cPath_new, 0, 1) == '_') {
$cPath_new = substr($cPath_new, 1);
}
}
}

return 'cPath=' . $cPath_new;
}

function tep_get_all_get_params($exclude_array = '') {
global $HTTP_GET_VARS;

if ($exclude_array == '') $exclude_array = array();

$get_url = '';

reset($HTTP_GET_VARS);
while (list($key, $value) = each($HTTP_GET_VARS)) {
if (($key != tep_session_name()) && ($key != 'error') && (!in_array($key, $exclude_array))) $get_url .= $key . '=' . $value . '&';
}

return $get_url;
}

function tep_date_long($raw_date) {
if ( ($raw_date == '0000-00-00 00:00:00') || ($raw_date == '') ) return false;

$year = (int)substr($raw_date, 0, 4);
$month = (int)substr($raw_date, 5, 2);
$day = (int)substr($raw_date, 8, 2);
$hour = (int)substr($raw_date, 11, 2);
$minute = (int)substr($raw_date, 14, 2);
$second = (int)substr($raw_date, 17, 2);

return strftime(DATE_FORMAT_LONG, mktime($hour, $minute, $second, $month, $day, $year));
}

////
// Output a raw date string in the selected locale date format
// $raw_date needs to be in this format: YYYY-MM-DD HH:MM:SS
// NOTE: Includes a workaround for dates before 01/01/1970 that fail on windows servers
function tep_date_short($raw_date) {
if ( ($raw_date == '0000-00-00 00:00:00') || ($raw_date == '') ) return false;

$year = substr($raw_date, 0, 4);
$month = (int)substr($raw_date, 5, 2);
$day = (int)substr($raw_date, 8, 2);
$hour = (int)substr($raw_date, 11, 2);
$minute = (int)substr($raw_date, 14, 2);
$second = (int)substr($raw_date, 17, 2);

if (@date('Y', mktime($hour, $minute, $second, $month, $day, $year)) == $year) {
return date(DATE_FORMAT, mktime($hour, $minute, $second, $month, $day, $year));
} else {
return ereg_replace('2037' . '$', $year, date(DATE_FORMAT, mktime($hour, $minute, $second, $month, $day, 2037)));
}

}

function tep_datetime_short($raw_datetime) {
if ( ($raw_datetime == '0000-00-00 00:00:00') || ($raw_datetime == '') ) return false;

$year = (int)substr($raw_datetime, 0, 4);
$month = (int)substr($raw_datetime, 5, 2);
$day = (int)substr($raw_datetime, 8, 2);
$hour = (int)substr($raw_datetime, 11, 2);
$minute = (int)substr($raw_datetime, 14, 2);
$second = (int)substr($raw_datetime, 17, 2);

return strftime(DATE_TIME_FORMAT, mktime($hour, $minute, $second, $month, $day, $year));
}

function tep_get_category_tree($parent_id = '0', $spacing = '', $exclude = '', $category_tree_array = '', $include_itself = false) {
global $languages_id;

if (!is_array($category_tree_array)) $category_tree_array = array();
if ( (sizeof($category_tree_array) < 1) && ($exclude != '0') ) $category_tree_array[] = array('id' => '0', 'text' => TEXT_TOP);

if ($include_itself) {
$category_query = tep_db_query("select cd.categories_name from " . TABLE_CATEGORIES_DESCRIPTION . " cd where cd.language_id = '" . (int)$languages_id . "' and cd.categories_id = '" . (int)$parent_id . "'");
$category = tep_db_fetch_array($category_query);
$category_tree_array[] = array('id' => $parent_id, 'text' => $category['categories_name']);
}

$categories_query = tep_db_query("select c.categories_id, cd.categories_name, c.parent_id from " . TABLE_CATEGORIES . " c, " . TABLE_CATEGORIES_DESCRIPTION . " cd where c.categories_id = cd.categories_id and cd.language_id = '" . (int)$languages_id . "' and c.parent_id = '" . (int)$parent_id . "' order by c.sort_order, cd.categories_name");
while ($categories = tep_db_fetch_array($categories_query)) {
if ($exclude != $categories['categories_id']) $category_tree_array[] = array('id' => $categories['categories_id'], 'text' => $spacing . $categories['categories_name']);
$category_tree_array = tep_get_category_tree($categories['categories_id'], $spacing . '&nbsp;&nbsp;&nbsp;', $exclude, $category_tree_array);
}

return $category_tree_array;
}

function tep_draw_products_pull_down($name, $parameters = '', $exclude = '') {
global $currencies, $languages_id;

if ($exclude == '') {
$exclude = array();
}

$select_string = '<select name="' . $name . '"';

if ($parameters) {
$select_string .= ' ' . $parameters;
}

$select_string .= '>';

$products_query = tep_db_query("select p.products_id, pd.products_name, p.products_price from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd where p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "' order by products_name");
while ($products = tep_db_fetch_array($products_query)) {
if (!in_array($products['products_id'], $exclude)) {
$select_string .= '<option value="' . $products['products_id'] . '">' . $products['products_name'] . ' (' . $currencies->format($products['products_price']) . ')</option>';
}
}

$select_string .= '</select>';

return $select_string;
}

function tep_options_name($options_id) {
global $languages_id;

$options = tep_db_query("select products_options_name from " . TABLE_PRODUCTS_OPTIONS . " where products_options_id = '" . (int)$options_id . "' and language_id = '" . (int)$languages_id . "'");
$options_values = tep_db_fetch_array($options);

return $options_values['products_options_name'];
}

function tep_values_name($values_id) {
global $languages_id;

$values = tep_db_query("select products_options_values_name from " . TABLE_PRODUCTS_OPTIONS_VALUES . " where products_options_values_id = '" . (int)$values_id . "' and language_id = '" . (int)$languages_id . "'");
$values_values = tep_db_fetch_array($values);

return $values_values['products_options_values_name'];
}

function tep_info_image($image, $alt, $width = '', $height = '') {
if (tep_not_null($image) && (file_exists(DIR_FS_CATALOG_IMAGES . $image)) ) {
$image = tep_image(DIR_WS_CATALOG_IMAGES . $image, $alt, $width, $height);
} else {
$image = TEXT_IMAGE_NONEXISTENT;
}

return $image;
}

function tep_break_string($string, $len, $break_char = '-') {
$l = 0;
$output = '';
for ($i=0, $n=strlen($string); $i<$n; $i++) {
$char = substr($string, $i, 1);
if ($char != ' ') {
$l++;
} else {
$l = 0;
}
if ($l > $len) {
$l = 1;
$output .= $break_char;
}
$output .= $char;
}

return $output;
}

function tep_get_country_name($country_id) {
$country_query = tep_db_query("select countries_name from " . TABLE_COUNTRIES . " where countries_id = '" . (int)$country_id . "'");

if (!tep_db_num_rows($country_query)) {
return $country_id;
} else {
$country = tep_db_fetch_array($country_query);
return $country['countries_name'];
}
}

function tep_get_zone_name($country_id, $zone_id, $default_zone) {
$zone_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country_id . "' and zone_id = '" . (int)$zone_id . "'");
if (tep_db_num_rows($zone_query)) {
$zone = tep_db_fetch_array($zone_query);
return $zone['zone_name'];
} else {
return $default_zone;
}
}

function tep_not_null($value) {
if (is_array($value)) {
if (sizeof($value) > 0) {
return true;
} else {
return false;
}
} else {
if ( (is_string($value) || is_int($value)) && ($value != '') && ($value != 'NULL') && (strlen(trim($value)) > 0)) {
return true;
} else {
return false;
}
}
}

function tep_browser_detect($component) {
global $HTTP_USER_AGENT;

return stristr($HTTP_USER_AGENT, $component);
}

function tep_tax_classes_pull_down($parameters, $selected = '') {
$select_string = '<select ' . $parameters . '>';
$classes_query = tep_db_query("select tax_class_id, tax_class_title from " . TABLE_TAX_CLASS . " order by tax_class_title");
while ($classes = tep_db_fetch_array($classes_query)) {
$select_string .= '<option value="' . $classes['tax_class_id'] . '"';
if ($selected == $classes['tax_class_id']) $select_string .= ' SELECTED';
$select_string .= '>' . $classes['tax_class_title'] . '</option>';
}
$select_string .= '</select>';

return $select_string;
}

function tep_geo_zones_pull_down($parameters, $selected = '') {
$select_string = '<select ' . $parameters . '>';
$zones_query = tep_db_query("select geo_zone_id, geo_zone_name from " . TABLE_GEO_ZONES . " order by geo_zone_name");
while ($zones = tep_db_fetch_array($zones_query)) {
$select_string .= '<option value="' . $zones['geo_zone_id'] . '"';
if ($selected == $zones['geo_zone_id']) $select_string .= ' SELECTED';
$select_string .= '>' . $zones['geo_zone_name'] . '</option>';
}
$select_string .= '</select>';

return $select_string;
}

function tep_get_geo_zone_name($geo_zone_id) {
$zones_query = tep_db_query("select geo_zone_name from " . TABLE_GEO_ZONES . " where geo_zone_id = '" . (int)$geo_zone_id . "'");

if (!tep_db_num_rows($zones_query)) {
$geo_zone_name = $geo_zone_id;
} else {
$zones = tep_db_fetch_array($zones_query);
$geo_zone_name = $zones['geo_zone_name'];
}

return $geo_zone_name;
}

function tep_address_format($address_format_id, $address, $html, $boln, $eoln) {
$address_format_query = tep_db_query("select address_format as format from " . TABLE_ADDRESS_FORMAT . " where address_format_id = '" . (int)$address_format_id . "'");
$address_format = tep_db_fetch_array($address_format_query);

$company = tep_output_string_protected($address['company']);
if (isset($address['firstname']) && tep_not_null($address['firstname'])) {
$firstname = tep_output_string_protected($address['firstname']);
$lastname = tep_output_string_protected($address['lastname']);
} elseif (isset($address['name']) && tep_not_null($address['name'])) {
$firstname = tep_output_string_protected($address['name']);
$lastname = '';
} else {
$firstname = '';
$lastname = '';
}
$street = tep_output_string_protected($address['street_address']);
$suburb = tep_output_string_protected($address['suburb']);
$city = tep_output_string_protected($address['city']);
$state = tep_output_string_protected($address['state']);
if (isset($address['country_id']) && tep_not_null($address['country_id'])) {
$country = tep_get_country_name($address['country_id']);

if (isset($address['zone_id']) && tep_not_null($address['zone_id'])) {
$state = tep_get_zone_code($address['country_id'], $address['zone_id'], $state);
}
} elseif (isset($address['country']) && tep_not_null($address['country'])) {
$country = tep_output_string_protected($address['country']);
} else {
$country = '';
}
$postcode = tep_output_string_protected($address['postcode']);
$zip = $postcode;

if ($html) {
// HTML Mode
$HR = '<hr>';
$hr = '<hr>';
if ( ($boln == '') && ($eoln == "\n") ) { // Values not specified, use rational defaults
$CR = '<br>';
$cr = '<br>';
$eoln = $cr;
} else { // Use values supplied
$CR = $eoln . $boln;
$cr = $CR;
}
} else {
// Text Mode
$CR = $eoln;
$cr = $CR;
$HR = '----------------------------------------';
$hr = '----------------------------------------';
}

$statecomma = '';
$streets = $street;
if ($suburb != '') $streets = $street . $cr . $suburb;
if ($country == '') $country = tep_output_string_protected($address['country']);
if ($state != '') $statecomma = $state . ', ';

$fmt = $address_format['format'];
eval("\$address = \"$fmt\";");

if ( (ACCOUNT_COMPANY == 'true') && (tep_not_null($company)) ) {
$address = $company . $cr . $address;
}

return $address;
}

////////////////////////////////////////////////////////////////////////////////////////////////
//
// Function : tep_get_zone_code
//
// Arguments : country country code string
// zone state/province zone_id
// def_state default string if zone==0
//
// Return : state_prov_code state/province code
//
// Description : Function to retrieve the state/province code (as in FL for Florida etc)
//
////////////////////////////////////////////////////////////////////////////////////////////////
function tep_get_zone_code($country, $zone, $def_state) {

$state_prov_query = tep_db_query("select zone_code from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country . "' and zone_id = '" . (int)$zone . "'");

if (!tep_db_num_rows($state_prov_query)) {
$state_prov_code = $def_state;
}
else {
$state_prov_values = tep_db_fetch_array($state_prov_query);
$state_prov_code = $state_prov_values['zone_code'];
}

return $state_prov_code;
}

function tep_get_uprid($prid, $params) {
$uprid = $prid;
if ( (is_array($params)) && (!strstr($prid, '{')) ) {
while (list($option, $value) = each($params)) {
$uprid = $uprid . '{' . $option . '}' . $value;
}
}

return $uprid;
}

function tep_get_prid($uprid) {
$pieces = explode('{', $uprid);

return $pieces[0];
}

function tep_get_languages() {
$languages_query = tep_db_query("select languages_id, name, code, image, directory from " . TABLE_LANGUAGES . " order by sort_order");
while ($languages = tep_db_fetch_array($languages_query)) {
$languages_array[] = array('id' => $languages['languages_id'],
'name' => $languages['name'],
'code' => $languages['code'],
'image' => $languages['image'],
'directory' => $languages['directory']);
}

return $languages_array;
}

function tep_get_category_name($category_id, $language_id) {
$category_query = tep_db_query("select categories_name from " . TABLE_CATEGORIES_DESCRIPTION . " where categories_id = '" . (int)$category_id . "' and language_id = '" . (int)$language_id . "'");
$category = tep_db_fetch_array($category_query);

return $category['categories_name'];
}

function tep_get_orders_status_name($orders_status_id, $language_id = '') {
global $languages_id;

if (!$language_id) $language_id = $languages_id;
$orders_status_query = tep_db_query("select orders_status_name from " . TABLE_ORDERS_STATUS . " where orders_status_id = '" . (int)$orders_status_id . "' and language_id = '" . (int)$language_id . "'");
$orders_status = tep_db_fetch_array($orders_status_query);

return $orders_status['orders_status_name'];
}

function tep_get_orders_status() {
global $languages_id;

$orders_status_array = array();
$orders_status_query = tep_db_query("select orders_status_id, orders_status_name from " . TABLE_ORDERS_STATUS . " where language_id = '" . (int)$languages_id . "' order by orders_status_id");
while ($orders_status = tep_db_fetch_array($orders_status_query)) {
$orders_status_array[] = array('id' => $orders_status['orders_status_id'],
'text' => $orders_status['orders_status_name']);
}

return $orders_status_array;
}

function tep_get_products_name($product_id, $language_id = 0) {
global $languages_id;

if ($language_id == 0) $language_id = $languages_id;
$product_query = tep_db_query("select products_name from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_name'];
}

function tep_get_products_description($product_id, $language_id) {
$product_query = tep_db_query("select products_description from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_description'];
}

// BoF Header Tag Controller
function tep_get_products_head_title_tag($product_id, $language_id) {
$product_query = tep_db_query("select products_head_title_tag from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_head_title_tag'];
}

function tep_get_products_head_desc_tag($product_id, $language_id) {
$product_query = tep_db_query("select products_head_desc_tag from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_head_desc_tag'];
}

function tep_get_products_head_keywords_tag($product_id, $language_id) {
$product_query = tep_db_query("select products_head_keywords_tag from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_head_keywords_tag'];
}
// EoF Header Tag Controller


function tep_get_products_url($product_id, $language_id) {
$product_query = tep_db_query("select products_url from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "' and language_id = '" . (int)$language_id . "'");
$product = tep_db_fetch_array($product_query);

return $product['products_url'];
}

////
// Return the manufacturers URL in the needed language
// TABLES: manufacturers_info
function tep_get_manufacturer_url($manufacturer_id, $language_id) {
$manufacturer_query = tep_db_query("select manufacturers_url from " . TABLE_MANUFACTURERS_INFO . " where manufacturers_id = '" . (int)$manufacturer_id . "' and languages_id = '" . (int)$language_id . "'");
$manufacturer = tep_db_fetch_array($manufacturer_query);

return $manufacturer['manufacturers_url'];
}

////
// Wrapper for class_exists() function
// This function is not available in all PHP versions so we test it before using it.
function tep_class_exists($class_name) {
if (function_exists('class_exists')) {
return class_exists($class_name);
} else {
return true;
}
}

////
// Count how many products exist in a category
// TABLES: products, products_to_categories, categories
function tep_products_in_category_count($categories_id, $include_deactivated = false) {
$products_count = 0;

if ($include_deactivated) {
$products_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c where p.products_id = p2c.products_id and p2c.categories_id = '" . (int)$categories_id . "'");
} else {
$products_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c where p.products_id = p2c.products_id and p.products_status = '1' and p2c.categories_id = '" . (int)$categories_id . "'");
}

$products = tep_db_fetch_array($products_query);

$products_count += $products['total'];

$childs_query = tep_db_query("select categories_id from " . TABLE_CATEGORIES . " where parent_id = '" . (int)$categories_id . "'");
if (tep_db_num_rows($childs_query)) {
while ($childs = tep_db_fetch_array($childs_query)) {
$products_count += tep_products_in_category_count($childs['categories_id'], $include_deactivated);
}
}

return $products_count;
}

////
// Count how many subcategories exist in a category
// TABLES: categories
function tep_childs_in_category_count($categories_id) {
$categories_count = 0;

$categories_query = tep_db_query("select categories_id from " . TABLE_CATEGORIES . " where parent_id = '" . (int)$categories_id . "'");
while ($categories = tep_db_fetch_array($categories_query)) {
$categories_count++;
$categories_count += tep_childs_in_category_count($categories['categories_id']);
}

return $categories_count;
}

////
// Returns an array with countries
// TABLES: countries
function tep_get_countries($default = '') {
$countries_array = array();
if ($default) {
$countries_array[] = array('id' => '',
'text' => $default);
}
$countries_query = tep_db_query("select countries_id, countries_name from " . TABLE_COUNTRIES . " order by countries_name");
while ($countries = tep_db_fetch_array($countries_query)) {
$countries_array[] = array('id' => $countries['countries_id'],
'text' => $countries['countries_name']);
}

return $countries_array;
}

////
// return an array with country zones
function tep_get_country_zones($country_id) {
$zones_array = array();
$zones_query = tep_db_query("select zone_id, zone_name from " . TABLE_ZONES . " where zone_country_id = '" . (int)$country_id . "' order by zone_name");
while ($zones = tep_db_fetch_array($zones_query)) {
$zones_array[] = array('id' => $zones['zone_id'],
'text' => $zones['zone_name']);
}

return $zones_array;
}

function tep_prepare_country_zones_pull_down($country_id = '') {
// preset the width of the drop-down for Netscape
$pre = '';
if ( (!tep_browser_detect('MSIE')) && (tep_browser_detect('Mozilla/4')) ) {
for ($i=0; $i<45; $i++) $pre .= '&nbsp;';
}

$zones = tep_get_country_zones($country_id);

if (sizeof($zones) > 0) {
$zones_select = array(array('id' => '', 'text' => PLEASE_SELECT));
$zones = array_merge($zones_select, $zones);
} else {
$zones = array(array('id' => '', 'text' => TYPE_BELOW));
// create dummy options for Netscape to preset the height of the drop-down
if ( (!tep_browser_detect('MSIE')) && (tep_browser_detect('Mozilla/4')) ) {
for ($i=0; $i<9; $i++) {
$zones[] = array('id' => '', 'text' => $pre);
}
}
}

return $zones;
}

////
// Get list of address_format_id's
function tep_get_address_formats() {
$address_format_query = tep_db_query("select address_format_id from " . TABLE_ADDRESS_FORMAT . " order by address_format_id");
$address_format_array = array();
while ($address_format_values = tep_db_fetch_array($address_format_query)) {
$address_format_array[] = array('id' => $address_format_values['address_format_id'],
'text' => $address_format_values['address_format_id']);
}
return $address_format_array;
}

////
// Alias function for Store configuration values in the Administration Tool
function tep_cfg_pull_down_country_list($country_id) {
return tep_draw_pull_down_menu('configuration_value', tep_get_countries(), $country_id);
}

function tep_cfg_pull_down_zone_list($zone_id) {
return tep_draw_pull_down_menu('configuration_value', tep_get_country_zones(STORE_COUNTRY), $zone_id);
}

function tep_cfg_pull_down_tax_classes($tax_class_id, $key = '') {
$name = (($key) ? 'configuration[' . $key . ']' : 'configuration_value');

$tax_class_array = array(array('id' => '0', 'text' => TEXT_NONE));
$tax_class_query = tep_db_query("select tax_class_id, tax_class_title from " . TABLE_TAX_CLASS . " order by tax_class_title");
while ($tax_class = tep_db_fetch_array($tax_class_query)) {
$tax_class_array[] = array('id' => $tax_class['tax_class_id'],
'text' => $tax_class['tax_class_title']);
}

return tep_draw_pull_down_menu($name, $tax_class_array, $tax_class_id);
}

////
// Function to read in text area in admin
function tep_cfg_textarea($text) {
return tep_draw_textarea_field('configuration_value', false, 35, 5, $text);
}

function tep_cfg_get_zone_name($zone_id) {
$zone_query = tep_db_query("select zone_name from " . TABLE_ZONES . " where zone_id = '" . (int)$zone_id . "'");

if (!tep_db_num_rows($zone_query)) {
return $zone_id;
} else {
$zone = tep_db_fetch_array($zone_query);
return $zone['zone_name'];
}
}

////
// Sets the status of a banner
function tep_set_banner_status($banners_id, $status) {
if ($status == '1') {
return tep_db_query("update " . TABLE_BANNERS . " set status = '1', expires_impressions = NULL, expires_date = NULL, date_status_change = NULL where banners_id = '" . $banners_id . "'");
} elseif ($status == '0') {
return tep_db_query("update " . TABLE_BANNERS . " set status = '0', date_status_change = now() where banners_id = '" . $banners_id . "'");
} else {
return -1;
}
}

////
// Sets the sort order of a product
function tep_set_product_sort_order($products_id, $sort_order) {
return tep_db_query("update " . TABLE_PRODUCTS . " set products_sort_order = '" . $sort_order . "', products_last_modified = now() where products_id = '" . (int)$products_id . "'");
}


////
// Sets the status of a product
function tep_set_product_status($products_id, $status) {
if ($status == '1') {
return tep_db_query("update " . TABLE_PRODUCTS . " set products_status = '1', products_last_modified = now() where products_id = '" . (int)$products_id . "'");
} elseif ($status == '0') {
return tep_db_query("update " . TABLE_PRODUCTS . " set products_status = '0', products_last_modified = now() where products_id = '" . (int)$products_id . "'");
} else {
return -1;
}
}

////
// Sets the status of a product on special
function tep_set_specials_status($specials_id, $status) {
if ($status == '1') {
return tep_db_query("update " . TABLE_SPECIALS . " set status = '1', expires_date = NULL, date_status_change = NULL where specials_id = '" . (int)$specials_id . "'");
} elseif ($status == '0') {
return tep_db_query("update " . TABLE_SPECIALS . " set status = '0', date_status_change = now() where specials_id = '" . (int)$specials_id . "'");
} else {
return -1;
}
}

////
// Sets timeout for the current script.
// Cant be used in safe mode.
function tep_set_time_limit($limit) {
if (!get_cfg_var('safe_mode')) {
set_time_limit($limit);
}
}

////
// Alias function for Store configuration values in the Administration Tool
function tep_cfg_select_option($select_array, $key_value, $key = '') {
$string = '';

for ($i=0, $n=sizeof($select_array); $i<$n; $i++) {
$name = ((tep_not_null($key)) ? 'configuration[' . $key . ']' : 'configuration_value');

$string .= '<br><input type="radio" name="' . $name . '" value="' . $select_array[$i] . '"';

if ($key_value == $select_array[$i]) $string .= ' CHECKED';

$string .= '> ' . $select_array[$i];
}

return $string;
}

////
// Alias function for module configuration keys
function tep_mod_select_option($select_array, $key_name, $key_value) {
reset($select_array);
while (list($key, $value) = each($select_array)) {
if (is_int($key)) $key = $value;
$string .= '<br><input type="radio" name="configuration[' . $key_name . ']" value="' . $key . '"';
if ($key_value == $key) $string .= ' CHECKED';
$string .= '> ' . $value;
}

return $string;
}
// USPS Methods 2.5
// Alias function for Store configuration values in the Administration Tool
function tep_cfg_select_multioption($select_array, $key_value, $key = '') {
for ($i=0; $i<sizeof($select_array); $i++) {
$name = (($key) ? 'configuration[' . $key . '][]' : 'configuration_value');
$string .= '<br><input type="checkbox" name="' . $name . '" value="' . $select_array[$i] . '"';
$key_values = explode( ", ", $key_value);
if ( in_array($select_array[$i], $key_values) ) $string .= ' CHECKED';
$string .= '> ' . $select_array[$i];
}
$string .= '<input type="hidden" name="' . $name . '" value="--none--">';
return $string;
}
////
// Retreive server information
function tep_get_system_information() {
global $HTTP_SERVER_VARS;

$db_query = tep_db_query("select now() as datetime");
$db = tep_db_fetch_array($db_query);

list($system, $host, $kernel) = preg_split('/[\s,]+/', @exec('uname -a'), 5);

return array('date' => tep_datetime_short(date('Y-m-d H:i:s')),
'system' => $system,
'kernel' => $kernel,
'host' => $host,
'ip' => gethostbyname($host),
'uptime' => @exec('uptime'),
'http_server' => $HTTP_SERVER_VARS['SERVER_SOFTWARE'],
'php' => PHP_VERSION,
'zend' => (function_exists('zend_version') ? zend_version() : ''),
'db_server' => DB_SERVER,
'db_ip' => gethostbyname(DB_SERVER),
'db_version' => 'MySQL ' . (function_exists('mysql_get_server_info') ? mysql_get_server_info() : ''),
'db_date' => tep_datetime_short($db['datetime']));
}

function tep_generate_category_path($id, $from = 'category', $categories_array = '', $index = 0) {
global $languages_id;

if (!is_array($categories_array)) $categories_array = array();

if ($from == 'product') {
$categories_query = tep_db_query("select categories_id from " . TABLE_PRODUCTS_TO_CATEGORIES . " where products_id = '" . (int)$id . "'");
while ($categories = tep_db_fetch_array($categories_query)) {
if ($categories['categories_id'] == '0') {
$categories_array[$index][] = array('id' => '0', 'text' => TEXT_TOP);
} else {
$category_query = tep_db_query("select cd.categories_name, c.parent_id from " . TABLE_CATEGORIES . " c, " . TABLE_CATEGORIES_DESCRIPTION . " cd where c.categories_id = '" . (int)$categories['categories_id'] . "' and c.categories_id = cd.categories_id and cd.language_id = '" . (int)$languages_id . "'");
$category = tep_db_fetch_array($category_query);
$categories_array[$index][] = array('id' => $categories['categories_id'], 'text' => $category['categories_name']);
if ( (tep_not_null($category['parent_id'])) && ($category['parent_id'] != '0') ) $categories_array = tep_generate_category_path($category['parent_id'], 'category', $categories_array, $index);
$categories_array[$index] = array_reverse($categories_array[$index]);
}
$index++;
}
} elseif ($from == 'category') {
$category_query = tep_db_query("select cd.categories_name, c.parent_id from " . TABLE_CATEGORIES . " c, " . TABLE_CATEGORIES_DESCRIPTION . " cd where c.categories_id = '" . (int)$id . "' and c.categories_id = cd.categories_id and cd.language_id = '" . (int)$languages_id . "'");
$category = tep_db_fetch_array($category_query);
$categories_array[$index][] = array('id' => $id, 'text' => $category['categories_name']);
if ( (tep_not_null($category['parent_id'])) && ($category['parent_id'] != '0') ) $categories_array = tep_generate_category_path($category['parent_id'], 'category', $categories_array, $index);
}

return $categories_array;
}

function tep_output_generated_category_path($id, $from = 'category') {
$calculated_category_path_string = '';
$calculated_category_path = tep_generate_category_path($id, $from);
for ($i=0, $n=sizeof($calculated_category_path); $i<$n; $i++) {
for ($j=0, $k=sizeof($calculated_category_path[$i]); $j<$k; $j++) {
$calculated_category_path_string .= $calculated_category_path[$i][$j]['text'] . '&nbsp;&gt;&nbsp;';
}
$calculated_category_path_string = substr($calculated_category_path_string, 0, -16) . '<br>';
}
$calculated_category_path_string = substr($calculated_category_path_string, 0, -4);

if (strlen($calculated_category_path_string) < 1) $calculated_category_path_string = TEXT_TOP;

return $calculated_category_path_string;
}

function tep_get_generated_category_path_ids($id, $from = 'category') {
$calculated_category_path_string = '';
$calculated_category_path = tep_generate_category_path($id, $from);
for ($i=0, $n=sizeof($calculated_category_path); $i<$n; $i++) {
for ($j=0, $k=sizeof($calculated_category_path[$i]); $j<$k; $j++) {
$calculated_category_path_string .= $calculated_category_path[$i][$j]['id'] . '_';
}
$calculated_category_path_string = substr($calculated_category_path_string, 0, -1) . '<br>';
}
$calculated_category_path_string = substr($calculated_category_path_string, 0, -4);

if (strlen($calculated_category_path_string) < 1) $calculated_category_path_string = TEXT_TOP;

return $calculated_category_path_string;
}

function tep_remove_category($category_id) {
$category_image_query = tep_db_query("select categories_image from " . TABLE_CATEGORIES . " where categories_id = '" . (int)$category_id . "'");
$category_image = tep_db_fetch_array($category_image_query);

$duplicate_image_query = tep_db_query("select count(*) as total from " . TABLE_CATEGORIES . " where categories_image = '" . tep_db_input($category_image['categories_image']) . "'");
$duplicate_image = tep_db_fetch_array($duplicate_image_query);

if ($duplicate_image['total'] < 2) {
if (file_exists(DIR_FS_CATALOG_IMAGES . $category_image['categories_image'])) {
@unlink(DIR_FS_CATALOG_IMAGES . $category_image['categories_image']);
}
}

tep_db_query("delete from " . TABLE_CATEGORIES . " where categories_id = '" . (int)$category_id . "'");
tep_db_query("delete from " . TABLE_CATEGORIES_DESCRIPTION . " where categories_id = '" . (int)$category_id . "'");
tep_db_query("delete from " . TABLE_PRODUCTS_TO_CATEGORIES . " where categories_id = '" . (int)$category_id . "'");

if (USE_CACHE == 'true') {
tep_reset_cache_block('categories');
tep_reset_cache_block('also_purchased');
}
}

function tep_remove_product($product_id) {
$product_image_query = tep_db_query("select products_image from " . TABLE_PRODUCTS . " where products_id = '" . (int)$product_id . "'");
$product_image = tep_db_fetch_array($product_image_query);

$duplicate_image_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS . " where products_image = '" . tep_db_input($product_image['products_image']) . "'");
$duplicate_image = tep_db_fetch_array($duplicate_image_query);

if ($duplicate_image['total'] < 2) {
if (file_exists(DIR_FS_CATALOG_IMAGES . $product_image['products_image'])) {
@unlink(DIR_FS_CATALOG_IMAGES . $product_image['products_image']);
}
}

tep_db_query("delete from " . TABLE_SPECIALS . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_PRODUCTS . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_PRODUCTS_TO_CATEGORIES . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_PRODUCTS_DESCRIPTION . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_PRODUCTS_ATTRIBUTES . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_CUSTOMERS_BASKET . " where products_id = '" . (int)$product_id . "'");
tep_db_query("delete from " . TABLE_CUSTOMERS_BASKET_ATTRIBUTES . " where products_id = '" . (int)$product_id . "'");

$product_reviews_query = tep_db_query("select reviews_id from " . TABLE_REVIEWS . " where products_id = '" . (int)$product_id . "'");
while ($product_reviews = tep_db_fetch_array($product_reviews_query)) {
tep_db_query("delete from " . TABLE_REVIEWS_DESCRIPTION . " where reviews_id = '" . (int)$product_reviews['reviews_id'] . "'");
}
tep_db_query("delete from " . TABLE_REVIEWS . " where products_id = '" . (int)$product_id . "'");

if (USE_CACHE == 'true') {
tep_reset_cache_block('categories');
tep_reset_cache_block('also_purchased');
}
}

function tep_remove_order($order_id, $restock = false) {
if ($restock == 'on') {
$order_query = tep_db_query("select products_id, products_quantity from " . TABLE_ORDERS_PRODUCTS . " where orders_id = '" . (int)$