Sunday, August 9, 2009

How to view oscommerce/oscmax in dreamweaver

I've seen more than a few questions on how the oscommerce/oscmax template you build can be viewed in Dreamweaver's Design View or some similar HTML editor.

The short, simple answer is that it can't.

Here's why: oscommerce, oscmax, other shopping carts, or any database-driven solution for that matter - are dynamic in nature. Whether written in PHP or not, it's just a bunch of arguments that will execute based on what the visitor has done. In the case of PHP, the code needs to be processed by a PHP-enabled server that will send the appropriate HTML code to your browser. Even if you have such capabilities running locally, you still won't be able to do this with Dreamweaver because it's static.

For example, some code like the one I've posted here determines the final thing to show in the browser - the final HTML code - based on whether a customer is logged in or not. The products page code instructs to show the details relevant to the product or category you've clicked on. The server queries your database based on these conditions and sends the data to your browser. You can't do this with Dreameaver.

So you might be able to see some graphics and colors that are not dynamically generated. But with most pages, you'll see some blank tables in the good case and absolutely nothing in the worst case. Still, if you know HTML you shouldn't have trouble working in code view, and then test through your browser.

Oscmax/Oscommerce - Check if customer is logged in

Sometimes you might want to specify some processing to occur or something to only show up if the customer is logged in. So before you write anything you need to specify this login condition, or check if a login session has started.

This is much simpler to do than it seems. All you need is this line:

if ((tep_session_is_registered('customer_id')) && (!tep_session_is_registered('noaccount'))) {
your code
}

which basically tells the browser to check if the session is registered but make sure that it's an account (rather than PWA session).

In oscommerce, you don't need the "noaccount" session since there is no PWA allowed. So it would be:

if (tep_session_is_registered('customer_id')) {
your code
}

Replace "your code" with whatever code you have, and at the end don't forget to put the closing bracket or you'll get a parse error.

For example, the following code redirects a logged in customer to the new products page, and otherwise sends a user to the login page:

if ((tep_session_is_registered('customer_id')) && (!tep_session_is_registered('noaccount'))) {
tep_redirect(tep_href_link(FILENAME_PRODUCTS_NEW));
}
else {
tep_redirect(tep_href_link(FILENAME_LOGIN));
}

And in oscommerce:

if (tep_session_is_registered('customer_id')) {
tep_redirect(tep_href_link(FILENAME_NEW_PRODUCTS));
}
else {
tep_redirect(tep_href_link(FILENAME_LOGIN));
}

Enjoy!

Tuesday, July 28, 2009

Attaching to email a file uploaded in a form

I had to create a processing script that would attach to an e-mail the file a user uploaded through a contact-us style form. After more digging than I expected (most scripts simply upload the file to the server and leave it there; I wanted it e-mailed as an attachment) I found this script, which did exactly what I wanted, except that it allowed just one attachment. My solution had to have up to three attachments, and also accommodate more fields than just the comments textbox (i.e. phone, address, etc.).

Well, I must say this script laid much of the groundwork. With a few modifications I was able to get it to exactly what I needed. Hopefully these tips will help you, too...

Include additional fields
The way I did this was to first add the fields in the form, e.g.

Phone: <' input name="phone" type="text">

And so on for each field I wanted to add.

Then, in the processing script, I edited line 11:
$message = $_POST['message'];

To include any other fields I wanted to include in the message, e.g.

$message = 'Phone: ' . $_POST['phone'] . "\n" . 'Message: ' . $_POST['message'];

The plain text - Phone: - is what you want to appear in the e-mail next to the input. Not necessary but certainly convenient. Within the $_POST brackets put the input name in single quotes.

There's probably a more efficient way to loop through all the form fields and compile them into the message, but since I only had 4-5 fields I figured this would be the least time-consuming way.

Multiple file attachments
To add more attachments I first added them in the form, e.g.

<' input name="fileatt2" type="file">

Then in the processing script I defined all the variables for each file, e.g.

$fileatt2 = $_FILES['fileatt2']['tmp_name'];
$fileatt_type2 = $_FILES['fileatt2']['type'];
$fileatt_name2 = $_FILES['fileatt2']['name'];

I put these immediately after the first file's variables (beginning on line 17), although I don't think the order should matter.

Then, just under line 43, which reads:

$data = chunk_split(base64_encode($data));

I added the following code:

if (is_uploaded_file($fileatt2)) {
// Read the file to be attached ('rb' = read binary)
$file2 = fopen($fileatt2,'rb');
$data2 = fread($file2,filesize($fileatt2));
fclose($file2);
$data2 = chunk_split(base64_encode($data2));

$message .= "--{$mime_boundary}\n" .
"Content-Type: {$fileatt_type2};\n" .
" name=\"{$fileatt_name2}\"\n" .
//"Content-Disposition: attachment;\n" .
//" filename=\"{$fileatt_name2}\"\n" .
"Content-Transfer-Encoding: base64\n\n" .
$data2;

}

Which is really just a duplicate of the code for the first file, except for the closing mime-boundary. Of course, you should edit the variable names - $file, $data, $fileatt_name and so on.

Putting this just before the first file ensures that both are included in the message - otherwise you'll just get the first file attached. So, for any additional attachment fields you're adding, you should add the code in an order reversed from the one they appear in the form. For example for a third field, you'd add the same code with $fileatt3 but it would come before the code for $fileatt2.

Again, there's probably a more efficient way to loop through all the fields automatically, but I only had three so writing that would be too time-consuming - so for most standard forms, this should do.

Sunday, July 26, 2009

Prevent customer checkout - Oscmax

I've seen contributions that attempt to prevent checkout by adding some code to the "Checkout" button in the shopping cart page. This is good, but a customer can bypass this simply by clicking on a "Checkout" link anywhere else on the site - for example in the header if they are logged in.

Rather than modifying every single checkout link, a more effective way is to add some code in checkout_shipping.php - since this page is the first checkout stage for all users, signed in or not. Another proof of reliability: oscommerce coders themselves use this page to verify that a customer is logged in, sending them to the login page otherwise.

So how to add a condition? Say that using the distinct product count function, you want to prevent users from checking out if they added less than 5 distinct products to their cart...

Open catalog/checkout_shipping.php

Around line 35, find:
"if (!tep_session_is_registered('customer_id'))"

This redirects a customer to the login page and so we want our function, which will redirect to the shopping cart page, to be processed before that (i.e. before checking even if the customer is logged in, make sure they added enough items to the cart - otherwise don't bring them to login).

Before this add the following:

// check that required number of products has been met
if ($cart->count_contents_distinct() > 5) {
tep_redirect(tep_href_link(FILENAME_SHOPPING_CART));
}

Of course you should have the function in place or you'll get errors all over. For a how-to on that, check out this post.

Alternatively, you can substitute a variety of other conditions in there: show_total, count_contents - basically any function from catalog/includes/classes/shopping_cart.php and others.

The numbers are easily configurable as well - just substitute a variable like MIN_PRODUCTS and then define it in the corresponding language file (a more detailed tutorial to follow...)

Using two spry tabbed panels within the same site or page

***LATEST UPDATE*** If you're looking for a simple tabbed panels script, check out my other post about jQuery.

***UPDATED*** This post has been updated - see the "widget inside widget" section for details.


Dreamweaver's Spry Tabbed Panels is one of the most versatile scripts I've worked with. I've been able to implement it with a variety of other scripts with virtually no conflicts. It's even possible to have two or more tabbed panels with different styles in the same website, webpage, or even within each other - here's how.

Within the same website
Each spry tabbed panels widget is referenced in your html:

var TabbedPanels1 = new Spry.Widget.TabbedPanels("divID")

This tells to look for function name "spry.widget.tabbedpanels" in the js script file to determine which css styles to apply. That's why you'll first need two "SpryTabbedPanels.js" files, each referencing the appropriate css styles.

To do this, edit the lines this.tabSelectedClass, this.tabHoverClass, this.tabFocusedClass, this.panelVisibleClass (lines 11-14) to use the class names you want - you should create the css styles first though. Save them separately - for example as 'sprytabbedpanels1.js' and 'sprytabbedpanels2.js' and make sure you reference both files in your <'head'> tag.

Next, to connect each widget to the correct css styles, change the name in the html reference - for example to something like:
var TabbedPanels1 = new Spry.Widget.Panels("divID")

Then open the 'sprytabbedpanels.js' file that references the css classes that you want associated with the widget instance that you just changed, and replace all spry.widget.tabbedpanels with the new name - in this case Spry.Widget.Panels. This is a basic find-replace; nothing more. The name doesn't really matter, so long as it's consistent.

Quick summary:
1) Each "var TabbedPanels1 ..." line is referencing the same widget name as exist in the js file.
2) Lines 11-14 in each sprytabbedpanels.js file are referencing classes that exist in the stylesheet.
3) Each "var TabbedPanels1 ..." line is referencing the divID in the div that you want the styles to apply.
4) All js and stylesheets are referenced in your head tag of each page.

If you've done all of the above everything should work smoothly. I know it did for me...

Within the same page, separated
This is a bit more complicated but definitely possible. First, to avoid major functionality problems, the "var TabbedPanels1 ..." line should appear immediately after the closing "div" tag of each tabbed panels widget. Otherwise the browser can easily mess up your panels and content. Then follow the instructions above for multiple widgets within the same site and you should be good to go.

Within the same page, widget inside widget
I actually had a case where I had a tabbedpanels widget where each panel's content was another tabbedpanels widget! Here, I used all the techniques mentioned above, and was able to get everything working except for the different class styles to show for "hover" and "selected" states. My trick was to use pictures to overcome this issue, at least for the tabs. Creating images as if for a button, I added the following code - from Dreamweaver's menu bar widget - inside the <'li'> tag to get the tab to change to over state when a user clicks it:

'< onclick="MM_nbGroup('down','group1','button-id','path-to-over-image',state-id)" onmouseover="MM_nbGroup('over','group-id','path-to-over-image','',state-id)" onmouseout="MM_nbGroup('out')">< src="path-to-up-image" alt="" name="button-id" border="0" id="button-id" onload="MM_nbGroup('init','group1','button-id', 'path-to-up-image', '', state-id)">< /a>'

(Remember to remove the spaces when opening tags).

*** UPDATED ***
I just realized that my instructions for spry tabbed panels within spry tabbed panels imply that you should change the function name (spry.widget.tabbedpanels) in the html reference and javascript file. This should be done to achieve different styles as long as the widgets are not within each other. If they are, do not follow this step or your widget will not work! Accordingly, you don't need to change the css class names in the javascript code, either.

To achieve different styles for a widget inside another widget, you can add a new div with a class name defined in your regular stylesheet inside the < 'li > of each tab or < 'div > of each panel. This would work for the up-state only (i.e. until the tab is clicked). If you want the different style to persist when clicked on, use images as described above.

The quickest way to place a widget inside another widget is to open Design view, then choose the tab corresponding to the panel where you want the inner widget to appear, place the cursor inside the panel, and click on the insert widget button.

May seem complicated but easy if you understand how the script works. Anyway, enjoy!

When using "#" make the page refresh

I had an anchor link on a homepage that was to open a DHTML-style window. But to establish the link the <'a'> tag had href="#". This wasn't an issue except for when you first visited the homepage and clicked that link, the page would first refresh and then you'd need to click the link again to finally open the window. Once you were on the site, even if you re-visited the page, it would behave correctly. Not major, but most users were expected to click the link on the first page hit, and aside from being confusing it didn't look too professional.

At first I thought it was related to the homepage initially showing up as "www.websitename.com" and later as "www.websitename.com/index.php". But even when I changed some references to "www.websitename.com" (or to index.php) the problem persisted.

Then I tried emptying the href reference (e.g. href="") but that didn't work either. So I finally went ahead and eliminated the href completely. The remainder of my link tag looks exactly the same. Thought the feature wouldn't work, but it's running perfect. Problem solved!

Friday, July 17, 2009

Distinct "in cart" count in oscommerce

This is a tutorial on how to show a distinct count of the products added to the cart in oscommerce (my other post dealt with oscmax). The number generated here will correspond to the number of line items, disregarding quantities. For more information and an explanation of why you'd want this, see my previous post: Oscmax: how to show a distinct count of products in the cart.

Please note that I have not been able to test this code on a live server; rather, I've followed here the same adaptation that I had for oscmax.

*****

Open catalog/includes/classes/shopping_cart.php.

Search for "function numberOfItems" - should be around line 435.

A few lines under find:
return $total;
}

Just under add the following:

public function distinctNumberOfItems() {
$total = 0;

foreach ( $this->_contents as $product ) {
$total += 1;
}

return $total;
}

Save and upload.

Then wherever you want the count to appear, add the following code:

< ? php echo $osC_ShoppingCart->distinctnumberOfItems();

?>

Done and enjoy!

Sunday, July 5, 2009

Convert a non-BTS contribution to BTS

Overview
Oscmax uses the BTS system, which is a real time-saver for those who understand it (the idea of BTS is to separate the PHP processing code from the front-end HTML, and allow the designer to quickly setup a uniform "frame" for the whole site with the main_page.tpl.php file). But since Oscommerce does not use this system, one major flaw is that many - perhaps most - developers are unfamiliar with it, and contributions are made with the usual format in mind. This tutorial will provide a general overview on how to get such contributions to work with Oscmax. I'll be using the Recently Viewed (Sales Optimized) contribution as my example.

How to implement
1) Create a new PHP file and name it the same as the original file, except with the extension tpl.php. For example, my original file was recently_viewed.php, so the new one is recently_viewed.tpl.php. If using Dreamweaver, enter the code view and delete everything you see.
NOTE: do not simply rename your old file - this new file does not replace it! In the end you will be using both on your site.

2) Open the original file and find in the code the beginning of the HTML page - "!doctype html public" etc.

3) Next, delete everything from the beginning of the HTML, the entire <'head'> tag, until and including the opening <'body'> tag. In BTS, this part is already provided for by the main_page.tpl.php file, so you don't need it here again.
NOTE: in the recently viewed contribution, there isn't anything new in this part of the code. However, in other contributions there may be references in the tag to script files or stylesheets. In that case you need to copy these references to the <'head'> section of the main_page.tpl.php file. Do not discard these references or your code will not work!

4) If there is a reference to "header.php" delete that too. This is also part of main_page.

5) Now go to the end of your new document. Starting from "require" reference to "footer.php", delete everything - through and including the closing < / body > and < / html > tags and the end of the document. If there is no reference to the footer just delete everything below and including the closing HTML tags.

6) Go to the beginning of the code - there should be a line that references "application_top.php" and immediately under it a "require" command for the corresponding language file. Select all the code that remains except these two lines - they should stay in the original file. Cut it, and paste into the new document you created in step #1.

7) In your new file, go to the beginning of the code file and add the opening php tag - " < ? php " (no quotes). In Dreamweaver the code should appear color-coded correctly (i.e. not black - if it is you didn't cut/paste correctly or add the tag in the right place!) Save this file with the tpl.php extension to the directory where all your other template content files reside (not necessarily the same folder as main_page.tpl.php...) 8) Open includes/filenames.php and add the following line:

define('CONTENT_PAGENAME', 'pagename');


Replace "pagename" with the name of your file. For example, for recently viewed:

define('CONTENT_RECENTLY_VIEWED', 'recently_viewed');


9) Go back to the original file you started out with. Under the language reference, add the following lines:


$content = CONTENT_PAGENAME;

include (bts_select('main', $content_template)); // BTSv1.5

require(DIR_WS_INCLUDES . 'application_bottom.php');


Replace "pagename" with the name of your file - should match what you just added to filenames.php. For example, for recently viewed:


$content = CONTENT_RECENTLY_VIEWED;


10) Add a closing php tag - " ? > " (no quotes). Now in Dreamweaver's code view, everything should show up color-coded, not black - if it's black you either didn't add everything or added it in the wrong places...

That's it! Save everything, upload to your server, and voila! You've converted a non-BTS file to BTS.

What's next?
This is just the beginning. Now that your file is in BTS mode, you can edit the HTML to match the look-and-feel of the rest of your site...

How to show a distinct count of products in the cart

Overview
The standard Oscmax $cart->count_contents() function, which is used to display the number of items in a customer's shopping cart, has one major flaw: it incorporates counts of quantities in the total. First off, for stores that sell items priced by measurements - i.e. per inch, foot, yard etc. - using this will count the quantity of the unit rather than of products. For example, if a store sells wire by foot, and the customer places 5 feet of the same wire in the cart, the function will output "5 items in cart", whereas if the customer changes to 3 feet, it will output "3 items in cart". This is quite confusing - after all the customer didn't really alter the number of products in the cart... Plus, from a sales point of view, the number can escalate quickly, making the customer feel as if their cart is full and hurry to checkout (rather than spend more time browsing the catalog).

To resolve this, the following function will show the number of distinct products in a customer's cart - equivalent to the number of rows in the shopping cart page. Using the above example, it will show "1 item in cart" for the wire regardless of the quantity.

How to implement
1) Open the file catalog/includes/classes/shopping_cart.php

2) Find the following (without the quotes): "function count_contents()"

3) A few lines under find "return $total_items; }"

4) After that bracket ("}") Add the following:

//---

function count_contents_distinct() { // get number of different products in cart
$total_items = 0;
if (is_array($this->contents)) {
reset($this->contents);
while (list($products_id, ) = each($this->contents)) {
$total_items += $this->in_cart($products_id);
}
}
return $total_items;
}

//---

5) Then, wherever you want the count to appear (for example in shopping_cart.tpl.php) add the following code:



6) NOTES:
- It's better to create a new function than alter the existing count_contents one, since it's used in several other places for calculations etc.
- This does not group attributes, meaning that if the customer adds the same product with different attributes to the cart (i.e. wire --> blue and wire --> green) it will show as two different products. Basically if it shows as a new product in the shopping cart page, it will be counted separately here.

What's next?
You can take this a step further and count only specific items in the cart, then prevent the customer from checking out if a specific condition is not satisfied. More on that later...

P.S. This only works for Oscmax - however I plan to post the code for Oscommerce soon, so stay tuned!