X-Cart is an e-commerce package that is written in PHP with the use of Smarty tags for its template system. For about $200, it includes a lot of functionality out of the box, and you can buy prebuilt skins for another $60 or so. For those interested in SEO, there is a module called CDSEO that you can purchase by a third party that’ll change the URLs from including GET request variables to a folder style SEO friendly format for another $150 or so. After the add ons, you can have a cart up and running for about $400.
The smarty system leaves a lot to be desired, so beware that it’s going to take longer than you’d normally think if you want to skin the site yourself, especially the first time you work with X-cart. One of the biggest challenges is knowing which template to modify. X-cart helps with this by providing a “webmaster mode”, which changes the status bar to reflect the folder and template structure of a particular section of the page. It’s quite handy because many of the template files are in strange places, and there are a ton of different template files.
X-Cart also loads pages slower than you would expect from a PHP site, which might be because of Smarty templates, but I’m not certain of that.
Note: this article is referring to version 4.0. 4.1 is out now, but I’ve heard that the newer versions are somewhat unstable until they’ve been out there for a while. 4.1 hasn’t been around long enough for me to use in a production environment yet. These instructions will
likely help with 4.1 as well. Of course, you’ll need to modify them to fit the newer codebase.
One issue I’ve come across is the need to put a small thumbnail image for each product listed on the shopping cart checkout pages. Below is how I modified the code to allow a 2nd picture to be uploaded in X-Cart’s admin area. I use one upload for a 250 pixel wide picture on the product detail page, and a 70 pixel wide image on the cart checkout page.
This article is assuming that you know PHP pretty well and can either grasp the Smarty concepts easily or have a familiarity with them. If you need further explanation about Smarty, please comment and I’ll write more about that in another post.
——-
Modifying X-cart to allow 2 picture upload fields
First, back up your x-cart files and database in case something goes wrong.
Then, modify the following files:
In include/product_modify.php:
Add this code directly below the ‘if’ block with the comment “Check if image selected is not expired”:
If that comment can’t be found, look for the same block of code except that it’s looking for an imtype of “T” instead of “S”.
The two “if” blocks could probably be combined into 1, if you’d like. Please note that as is, you can only upload one image at a time.
If you want to be able to upload both at once, you’ll need to modify the code beyond the example to handle that.
//Cart Image
if ($file_upload_data["imtype"] == "S") {
if ($file_upload_data["counter"] == 1) {
$file_upload_data["counter"]++;
$smarty->assign("file_upload_data", $file_upload_data);
}
else {
if ($file_upload_data["source"] == "L")
@unlink($file_upload_data["file_path"]);
x_session_unregister("file_upload_data");
}
}
Also in include/product_modify.php, change the # Prepare for thumbnail updating (around line 278) to this:
# Prepare for thumbnail updating
$image_posted = func_check_image_posted($file_upload_data, "T");
$cartimage_posted = func_check_image_posted($file_upload_data, "S");
$store_in = ($config["Images"]["thumbnails_location"] == "FS"?"FS":"DB");
$cartstore_in = ($config["Images"]["cartimages_location"] == "FS"?"FS":"DB");
And add this code block directly after the $image_posted “if” block:
if ($cartimage_posted) {
$cartimage_data = func_get_image_content($file_upload_data, $productid);
$row_exists = func_query_first("select * from $sql_tbl[thumbnails] where productid = '$productid'");
if ($row_exists['productid'] == "") {
db_query("INSERT INTO $sql_tbl[thumbnails] (productid, ".($cartstore_in == "FS"?"cartimage_path":"cartimage").", cartimage_type) VALUES ('$productid', '$cartimage_data[image]', '$cartimage_data[image_type]')");
} else {
db_query("UPDATE $sql_tbl[thumbnails] SET ".($cartstore_in == "FS"?"cartimage_path":"cartimage")." = '$cartimage_data[image]', cartimage_type = '$cartimage_data[image_type]' WHERE productid = '$productid'");
}
}
On skin1/main/product_details.tpl, add this text below the normal thumbnail table row:
<!-- Cart Image -->
<TR>
{if $productids ne ''}<TD width="15" class="TableSubHead"> </TD>{/if}
<TD colspan="2">{include file="main/subheader.tpl" title="Small Cart Image"}</TD>
</TR>
<TR>
{if $productids ne ''}<TD width="15" class="TableSubHead"><INPUT type="checkbox" value="Y" name="fields[thumbnail]"></TD>{/if}
<TD class="ProductDetails" valign="top"><FONT class="FormButton">Small Image</FONT><BR>(recommended size 50x50 or smaller)</TD>
<TD class="ProductDetails">
{include file="product_cartthumbnail.tpl" productid=$product.productid product=$product.product}
<BR>
<TABLE border="0" cellpadding="0" cellspacing="0" width="100%">
<TR>
<TD>
<INPUT type="button" value="{$lng.lbl_change_image}" onclick='javascript: if (confirm("{$lng.txt_change_image_text|strip_tags}")){ldelim}popup_image_selection("S", "{$product.productid}", "{$query_string}");{rdelim}'>
<!--
<INPUT type="button" value="{$lng.lbl_delete_image}" onclick='javascript: if (confirm("{$lng.txt_change_image_text|strip_tags}")){ldelim}self.location="product_modify.php?mode=delete_thumbnail&productid={$product.productid}"{rdelim}'>
-->
</TD>
<TR>
{if $file_upload_data.file_path}
<TR>
<TD>
<BR><BR>
{$lng.txt_save_thumbnail_note}
</TD>
</TR>
<!-- End Cart Image -->
In include/image_selection.php, add this case in the $imtype switch:
case "S":
$config_data["location"] = $config["Images"]["cartimages_location"];
$config_data["path"] = $config["Images"]["cartimages_path"];
$config_data["path_only"] = $config["Images"]["cartimages_path_only"];
break;
Save image.php as cartimage.php, then change the code portion to the following (leave the x-cart license comment stuff alone):
Also note that I’ve included <?php even though it shows up above the comments. Of course, you only need this in one spot.
<?php
require "./top.inc.php";
require "./config.php";
if (empty($productid)) $productid = "";
$image_out = ""; $image_type = ""; $image_path = "";
if (!empty($tmp)) {
x_session_register("file_upload_data");
if (!empty($file_upload_data["file_path"]) && $file_upload_data["id"]==$productid && $file_upload_data["imtype"]=="S") {
$image_out = func_file_get($file_upload_data["file_path"], true);
}
}
if (empty($image_out)) {
if(!empty($variantid))
$result = db_query("SELECT cartimage as image, cartimage_path as image_path, cartimage_type as image_type FROM $sql_tbl[thumbnails] WHERE productid='$productid' AND variantid = '$variantid'");
if(empty($result))
$result = db_query("SELECT cartimage as image, cartimage_path as image_path, cartimage_type as image_type FROM $sql_tbl[thumbnails] WHERE productid='$productid' AND variantid = ''");
if (db_num_rows($result)) {
list($image, $image_path, $image_type) = db_fetch_row($result);
if ($image == "") {
header("Content-type: image/gif");
func_readfile($default_image, true);
exit;
}
} else {
header("Content-type: image/gif");
func_readfile($default_image, true);
//echo "Image: " . $image_type;
exit;
}
db_free_result($result);
if ($config["Images"]["thumbnails_location"] == "DB") {
if (!empty($image))
$image_out = $image;
else
$no_image_db = true;
}
if ($config["Images"]["thumbnails_location"] == "FS" || !empty($no_image_db)) {
if (!empty($image_path)) {
header("Content-type: $image_type");
func_readfile($image_path, true);
exit;
}
}
}
if (!empty($image_out)) {
header("Content-type: $image_type");
echo $image_out;
} else {
header("Content-type: image/gif");
func_readfile($default_image, true);
}
?>
Create a new file called skin1/product_cartthumbnail.tpl, and put this code in it:
{* $Id: product_cartthumbnail.tpl,v 1.14 2007/09/14 09:53:29 max Exp $ *}
{if $config.Appearance.show_thumbnails eq "Y"}
<IMG id="{$id}" src="{if $tmbn_url}{$tmbn_url}{else}{if $full_url}{$http_location}{else}{$xcart_web_dir}{/if}/cartimage.php?productid={$productid}{if $file_upload_data.file_path}&tmp=y{/if}{/if}" alt="{$product|escape}" border="0">
{/if}
In include/func.php, in the func_get_image_content function, add this to the $file_upload_data[”imtype”] switch:
case "S":
$config_data["location"] = $config["Images"]["cartimages_location"];
break;
In the database:
Modify xcart_thumbnails (assuming you used the prefix “xcart” when installing it, otherwise modify the table with the appropriate prefix)
to include the following fields:
cartimage mediumblob allow nulls
cartimage_path varchar(255) allow nulls
cartimage_type varchar(64) allow nulls
In xcart_config, add the following rows (modify the table name if you used a different prefix other than xcart):
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_location', 'location of small cart images', 'DB', 'Images', '130', 'text', 'DB')
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_path', 'FS junk - not needed', '', 'Images', '140', 'text', '')
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_path_only', 'FS junk - not needed', 'N', 'Images', '150', 'checkbox', 'N')
After that, test it and see if everything works. If not, you’ll need to troubleshoot the code to see where you missed something.
Good Luck,
Brian
Invision Powerboard 1.2 and 1.3 are still used by many people because they’re free, where the newest versions are not. However, the coding for them isn’t 100% compatible with the latest versions of PHP and MySQL. Many web hosting companies are moving to PHP5 and MySQL5, and this will break the default installation of IPB. Here is a collection of the changes you’ll need to make to your files so that IPB 1.2 or 1.3 will work with PHP5 & MySQL 4.1 or greater.
If your forum is experiencing any of these issues, the rest of this article may fix your problem. Make sure you back up your files and database before making any changes, just in case.
Symptoms:
Blank ‘Profile’ page
Blank ‘My Controls’ page
MySQL errors when reporting post
MySQL errors on ‘The Moderating Team’ page
Files affected:
sources/misc/contact_member.php
sources/Profile.php
sources/Usercp.php
sources/misc/stats.php
Step 1:
Open sources/Usercp.php
Find:
var $parser;
Change To:
//var $parser;
Save and upload sources/Usercp.php
Step 2:
Open sources/Profile.php
Find:
var $parser;
Change To:
//var $parser;
Save and upload sources/Profile.php
Step 3:
Open sources/misc/contact_member.php
Find:
var $email = “”;
var $forum = “”;
var $email = “”;
Change To:
//var $email = “”;
var $forum = “”;
var $email = “”;
Find:
$DB->query(”SELECT m.name, m.email, mod.member_id FROM gallerymoderators mod, gallerymembers m WHERE mod.forum_id=’$fid’ and mod.member_id=m.id”);
Change To:
$DB->query(”SELECT m.name, m.email, moder.member_id FROM gallerymoderators moder, gallerymembers m WHERE moder.forum_id=’$fid’ and moder.member_id=m.id”);
Save and upload sources/misc/contact_member.php
Step 4:
Open sources/misc/stats.php
Find:
//——————————————–
// Do we have any moderators? NORMAL MODS 1st
//——————————————–
$DB->query(”SELECT m2.id, m2.name, m2.email, m2.hide_email, m2.location, m2.aim_name, m2.icq_number,
f.id as forum_id, f.read_perms, f.name as forum_name, c.state
FROM gallerymoderators mod
LEFT JOIN galleryforums f ON(f.id=mod.forum_id)
LEFT JOIN gallerycategories c ON(c.id=f.category AND c.state != 0)
LEFT JOIN gallerymembers m2 ON (mod.member_id=m2.id)
“);
Change To:
//——————————————–
// Do we have any moderators? NORMAL MODS 1st
//——————————————–
$DB->query(”SELECT m2.id, m2.name, m2.email, m2.hide_email, m2.location, m2.aim_name, m2.icq_number,
f.id as forum_id, f.read_perms, f.name as forum_name, c.state
FROM gallerymoderators moder
LEFT JOIN galleryforums f ON(f.id=moder.forum_id)
LEFT JOIN gallerycategories c ON(c.id=f.category AND c.state != 0)
LEFT JOIN gallerymembers m2 ON (moder.member_id=m2.id)
“);
Find: (this edit isn’t in 1.2, so you should be able to skip this step if that’s your version)
//——————————————–
// Do we have any moderators? GROUP MODS 1st
//——————————————–
$DB->query(”SELECT m.id, m.name, m.email, m.hide_email, m.location, m.aim_name, m.icq_number,
f.id as forum_id, f.read_perms, f.name as forum_name, c.state
FROM gallerymoderators mod
LEFT JOIN galleryforums f ON(f.id=mod.forum_id)
LEFT JOIN gallerycategories c ON(c.id=f.category AND c.state != 0)
LEFT JOIN gallerymembers m ON ((mod.is_group=1 and mod.group_id=m.mgroup))
“);
Change To:
//——————————————–
// Do we have any moderators? GROUP MODS 1st
//——————————————–
$DB->query(”SELECT m.id, m.name, m.email, m.hide_email, m.location, m.aim_name, m.icq_number,
f.id as forum_id, f.read_perms, f.name as forum_name, c.state
FROM gallerymoderators moder
LEFT JOIN galleryforums f ON(f.id=moder.forum_id)
LEFT JOIN gallerycategories c ON(c.id=f.category AND c.state != 0)
LEFT JOIN gallerymembers m ON ((moder.is_group=1 and moder.group_id=m.mgroup))
“);
Save and upload sources/misc/stats.php
Step 5:
Add the following code to the top of index.php and admin.php:
//– mod_php5 begin
$HTTP_SERVER_VARS = isset($_SERVER)?$_SERVER:array();
$HTTP_GET_VARS = isset($_GET)?$_GET:array();
$HTTP_POST_VARS = isset($_POST)?$_POST:array();
$HTTP_POST_FILES = isset($_FILES)?$_FILES:array();
$HTTP_COOKIE_VARS = isset($_COOKIE)?$_COOKIE:array();
$HTTP_ENV_VARS = isset($_ENV)?$_ENV:array();
$HTTP_SESSION_VARS = isset($_SESSION)?$_SESSION:array();
//– mod_php5 end
———
Portions of this article was paraphrased from a post by FuSoYa at http://forums.invisionize.com/index.php?showtopic=73882
I’m often surprised by the number of people that still have not heard of Ruby on Rails.
In case you haven’t heard of Rails, it’s a framework built upon the Ruby programming language. Ruby is completely object-oriented and suitable for many platforms and purposes. It can be run on all the major operating systems, and comes preinstalled on most modern flavors of Linux. Rails is a framework that makes developing web applications in Ruby take less time once you get past its learning curve.
To date, I’ve programmed in several languages. I wrote a web spider in Java, built a very popular tech support portal in ASP (in the top 3000 of Alexa’s ranking), have written countless export and database administration scripts in Perl, and have written a lot of applications in PHP such as a point of sale system, photography gallery, online appointment system, employee management system, file management system, and numerous other websites over the last 9 years.
I’ve felt that PHP has been somewhat stagnate for the past couple of years. Sure, there is version 5 that does a little more with objects and classes, but my impression is that the majority of PHP sites still run on version 4, which was released in May of 2000 even though version 5 came out in July of 2004. No doubt, PHP has been adding support for various extensions, and they’ve added additional functionality in their many sub-releases, but it’s essentially the same as it was 7 years ago.
In the first draft of this document, I stated that PHP replaced Perl, and Ruby on Rails will eventually replace PHP. Technically, that’s incorrect. PHP hasn’t replaced Perl. Rather, I believe that the developers of PHP based a lot of their functionality on Perl, but changed a few things based on what they intended to be a better system for web development. This is only my opinion, so please take that with a grain of salt as my experience developing web applications in Perl is limited.
What I can tell you with certainty is that the developers of Ruby on Rails did their homework. They’ve taken a look at all of the existing web development platforms such as Java, PHP, Perl, ASP, ASP.NET, Coldfusion, Python, etc and have a really good idea of the things web developers of these platforms had to do over and over again such as creating forms or writing dynamic database queries with optional fields in the where clause, etc. Either through personal experience or by viewing other’s code, I’m not sure, but they also knew that most ASP/PHP style sites with embedded code within HTML causes a lot of headaches on larger sites because it’s a lot harder to maintain and test effectively.
From there, they created a framework on top of the Ruby language that forces code to be separated into 3 distinct areas: The models contain database info, the controllers contain application logic, and the views contain the html templates. This makes applications more structured and easier to maintain. They also created a set of libraries that make other aspects of web development easier to deal with. ActiveRecord does a really good job of abstracting database queries. Not only does this make creating queries easier, but it makes it so the application will work for a number of different database servers without adding a bunch of kludges for each database server. There is also a feature called routes which reduces the need for GET variables in a URL string. Instead, URLs follow folder style syntax, which has been claimed in many places to be more search engine friendly.
Ruby on Rails has caused a stir in the web development community, and many businesses are moving from PHP development to Ruby on Rails development. Why? Because once you know Ruby on Rails, you can program an application faster than PHP.
In response, the PHP community is trying to catch up, but they have a long way to go, and in my opinion, they’re going to fall on their face. They aren’t unified. They aren’t creating 1 framework to rival RoR or the other main platforms such as ASP.NET or Java. I did a couple of searches and found over 40 different frameworks for PHP.
With over 40 to choose from, how can a person decide on the right one? There’s definitely a learning curve involved, so who has the time to learn 40 different frameworks? How many people are getting turned down for jobs because they know PHP, but not the right framework that the business is using? How many of these frameworks are going to still be supported in 2 or more years? The worst part of it is that many of the PHP frameworks (CakePHP comes to mind) openly claim to be a Rails clone. Why use a clone when you can use the real thing?
“United we stand, Divided we fall”.
Ruby on Rails is united. There is one framework to learn and enhance. PHP is divided with over 40 frameworks.
I still program in PHP for legacy sites, but new sites are being done in Ruby on Rails.
Ruby on Rails is not all fluffy clouds, girls fanning you with giant leaves, or whatever your personal paradise is, however.
It’s a huge pain to get the server set up just right, and overall, it isn’t as fast as raw PHP (or raw Ruby for that matter). Typically, the lower level your code is, the faster it is because it doesn’t have to compute as much. Rails has several layers of code sitting between your own code and the processor. It also has a pretty steep learning curve unless you’ve already programmed using a framework and are familiar with object oriented concepts. It’s also really new, which means that there isn’t a lot of support on the web for Rails, and many of the standard apps that people can expect to get on PHP aren’t available yet such as free forums, ecommerce sites, etc. There are a few available, but they aren’t developed as fully yet. It’s also more difficult to find a hosting company that supports Rails unless you have a dedicated server and do it yourself. They can be found, but there is only a handful so far.
Rails does provide some great features to scale a site, however. Sessions can be stored in a database, which makes it possible to have one web application span multiple servers and not worry about sessions getting lost. There’s support for caching at a page level and at a partial level (a partial in Rails is like an include or require in PHP). Currently, the best server setup uses a combination of Apache and Mongrel, a single-threaded web server designed to handle Rails applications. Apache’s proxy load balancer makes it possible to pass connections to any number of instances of Mongrel whether on the same or different servers, which makes it possible to scale a site quite easily.
Frameworks are the way to go, if you want the structure of your code to be standard and thus easier to get a new developer ramped up and productive faster. Ruby on Rails is ahead of the game and PHP is trying to catch up. Every day, people are switching from PHP development to Ruby, and unless they can come up with a unified framework to rival Rails, I believe they will lose a large percentage of their development community.
I’ve written a 2009 update to this article, also titled Ruby on Rails vs. PHP on a social network site that I wrote using Ruby on Rails.