Sunday, January 3, 2010

Dynamically create multiple directories

I was looking for an easy way to dynamically create multiple directories with PHP, for example a directory and then sub-directories within the same script. Couldn't find anything so I came up with something on my own:

$first_directory = 'path/to/the/directory';
$second_directory = 'name-of-second-directory-relative-to-first';
mkdir($first_directory);
chdir($first_directory);
mkdir($second_directory);

What this does is create the first directory (path should be relative to the folder where the file calling your script resides), and move to that directory with chdir. Then the second directory is created within the first, path should be relative to the latter.

Of course there are many other applications. Enjoy!

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

***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!