Saturday, August 21, 2010

.htaccess - DirectoryIndex with rewrite

I was looking for a way to not only set the default landing page for a directory, but also get that page name to show in the browser's URL area (basically with rewrite). Googling and browsing through the Apache manual didn't seem to give an answer, so here's what I did...

RewriteRule ^folder/$ /folder/defaultpage [R]

Seems obvious, but it was an "Aha!" moment for me. The folder path would be relative to root - if someone tries to access the folder, redirect them to the page within the folder. Virtually the same length as DirectoryIndex, but with a twist - enjoy!

A better "tabbed panels" script with jQuery

I've been getting quite a few comments on my post about using Dreamweaver's tabbed panels widget. While it's a decent widget, I've been frustrated with the lack of control and flexibility it gave me. After hearing a lot about the wonders of jQuery, I thought I'll give it a try.

Picking up jQuery took some time. I'm not great with javascript, and being used to the dynamic abilities of server-side languages (such as PHP) made me feel like I'm taking a step backwards. So my reluctance made me integrate some jQuery UI widgets into my applications and leave it at that.

Although jQuery UI was definitely better than Dreamweaver's widgets, I found myself again yearning for more control. With my knowledge of jQuery at that point, I realized that the tabbed panels script I needed could take just a few lines of code... And I wrote one from scratch.

So there it is - a really simple, highly customizable, ultra lightweight (4 lines of javascript!) tabbed panels "widget". I'm putting widget in quotes because it's so short, it barely feels like one. Enjoy!

The HTML
Each tab title has its own div and corresponding div that holds the related content. All of the divs that hold a tab title must share the same class, in my example it's "head". Also, each div has a unique id, whose value must match the id of the corresponding content div with some predetermined prefix, in my case it's 'content'. You can have any value whatsoever, so long as they match. The order of the divs doesn't matter, so if you want this to look more like an accordion rather than tabbed panels just put each content div right after its title, rather than grouping all the titles and content separately.
If you need the id attribute for the CSS, you can use the name attribute to hold a unique value instead.

< 'div' class="head" id="tab_1">Tab 1 Title< /div >
< 'div' class="head" id="tab_2">Tab 2 Title< /div >
< 'div' class="head" id="tab_3">Tab 3 Title< /div >
< 'div' class="content" id="tab_1_content">Tab 1 Content< /div >
< 'div' class="content" id="tab_2_content">Tab 2 Content< /div >
< 'div' class="content" id="tab_3_content">Tab 3 Content< /div >


The CSS
The only thing you need if you don't want all the content to display when it's first loading:

.content { display: none; }

Other than that, you have the freedom to set your own styles - let the creativity roll!

The jQuery
Well I promised 4 lines of code so I can't make this part too complicated either. All you need:

$('.head').click(function() {
var name = '#' + $(this).attr('id') + '_content';
$('div.content').not(name).hide();
$(name).show(); });

So basically, when you click on one of the head divs, get the id of that div (or name if you need the id for styling), show the content div with that id, and hide all the content divs whose id doesn't match that id - or all the other content divs. Didn't I promise simple?

Advanced Stuff
You can customize the jQuery with different effects instead of show and hide, or add delays to make the transitions look more smooth.

To set a default tab to show, add any unique attribute to that content div and use jQuery to show it as soon as the page loads:

$('#default_tab').load(function() {
$(this).show();
});


If you want a different look for the tabs on mouseover:

$('.head').hover(function() {
$(this).addClass('mouseoverclass') },
function() {
$(this).removeClass('mouseoverclass');
});

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...)