Introduction
This document will guide you through integrating WebConnect with an online shopping system.
WebConnect allows shop administrators to synchronise their online stores with their back-office accounting systems. Rather than maintaining inventory on their website manually, they can update prices, stock levels, descriptions, images and more directly in their accounting packages, and have those changes pushed to their websites.
They can also push catalogues, customer information and files attached to products, and when customers place orders, those orders can be sent back into their accounting system as sales orders.
As a web developer, your task will be to integrate the WebConnect PHP library with an existing website, platform or e-commerce package.
Netfira have developed several plugins for popular e-commerce packages, including Magento, osCommerce and Zen Cart. If you're like most programmers, you'll want to skip past the docs and go straight to the code examples. We're also software developers and we feel the same way, so we've included all our code for osCommerce and Zen Cart for you to inspect. It's full of useful comments that should make it pretty easy to get the hang of WebConnect.
If you fancy yourself a PHP guru, you may want to take a look at webconnect.docstub.php. It's a mock-up of the entire WebConnect library with PHPDoc documentation that will give you a very fast, thorough overview of the library's capabilities.
To write a WebConnect plugin for an e-commerce application, you'll need:
Some basic knowledge of HTML, JavaScript and SQL will also be helpful, but not essential.
We've put tons of effort into making the WebConnect library incredibly fast, easy and intuitive to work with. There are only a few things you'll need to do to develop a fully-functional plugin:
We also have a couple of user interfaces you can embed in your application (and customise if you like) to help users pair up information from both systems, if need be. But for now, let's jump straight in with an example that demonstrates how easy it's going to be to integrate WebConnect with your application.
Before we get into too much detail, let's look at how you might take product information from WebConnect and display it in your e-commerce application.
When your seller's PC sends data to WebConnect, it communicates directly with webconnect.php. The webconnect.php file is the entire WebConnect PHP library. It begins with a reference to webconnect.config.php, which is where we'll configure WebConnect to work with our host system.
Open webconnect.config.php in your editor of choice. Out of the box, it'll have the configuration variables and their default values intermingled with some explanatory comments. Let's delete all that and start from scratch.
Ordinarily we'd use this file to fire up the host system and ask it for database access details, but for now, we'll just code them in.
<?php // webconnect.config.php Netfira::$syncPassword = 'Passw0rd'; Netfira::$dbType = Netfira::DB_TYPE_MYSQL; Netfira::$dbHost = 'localhost'; Netfira::$dbUser = 'root'; Netfira::$dbPass = ''; Netfira::$dbData = 'MyShop';
All we've done here is tell Netfira how to connect to our database, what kind of database it's talking to, and set the password that must be supplied by the seller when they try to send or receive data.
Next, we'll create a function that WebConnect can call when it receives new product information:
function onReceiveProduct($data)
{
$query = sprintf(
"INSERT INTO products (`product_name`, `unit_price`, `stock`) VALUES('%s', %s, %s)",
mysql_real_escape_string($data->description),
$data->unitPrice,
$data->stock
);
mysql_query($query);
}
This function creates a new product record in our make-believe e-commerce application, copies some data into it from WebConnect, and saves it to the host app's database.
Finally, we'll register our function with WebConnect as an event handler:
Netfira::addEventHandler(NF_RECEIVE_PRODUCT, 'onReceiveProduct');
This line tells WebConnect than when a new product is received, we want it to call the onReceiveProduct function. Event handlers don't have to be global functions; they can be instance methods, static methods or even lambda functions in PHP ≥ 5.3.
Now, when a seller points their PC to your copy of webconnect.php and adds a new product to their accounting system, it will appear in your e-commerce application almost instantly. It really is that easy!
Good news, sports fans! Although the other half of the system you're developing is an expensive accounting package with the Netfira Seller desktop application running along side it, you won't be needing either to test your plugin.
The WebConnect PHP library comes with a built-in testing application for emulating interactions between your software and the seller's PC. Let's open it up and test the code we just wrote. Type this URL into your browser:
http://localhost/webconnect.php?pw=Passw0rd&res=test.html
Naturally, if your webconnect.php is located elsewhere, enter the correct path.
Notice we included the password we set in webconnect.config.php. You'll need to authenticate any time you make requests to webconnect.php.
You should see the WebConnect TestCentre application. Create a new test, and enter the following test code:
UPDATE Product gapple description = Granny Smith Apples price = 1.50 stock = 66 COMMIT
Don't worry about capitalisation or white space – it's all for readability and can be formatted as you like.
Here we're instructing TestCentre to update the product with id "gapple" by setting its description, price and stock level, and committing the changes. The COMMIT command lets you queue up multiple changes and send them as a single HTTP request.
When you're happy with your test script, click "Run". As long as the onReceiveProduct function is able to run (you'll need to change it to suit your application's API), you'll have a shiny new product in your store to start selling.
Of course, the handler we just wrote only creates new products – it doesn't update existing ones. To do that, we'll need to look at how WebConnect pairs its records with the records in your e-commerce app. We'll look at pairing in detail in section 3. But first, let's have a look at the rest of WebConnect's configuration options.
Configuration
In the previous example, we assigned our database connection settings directly. This would work for a standalone application, but not for a plugin where these details are likely to be configured by the site owner. Similarly, the seller's password should be an option in your e-commerce application's admin interface.
How you obtain this information is entirely up to you, but most e-commerce systems provide ways to do this through their code.
Lets say your application defines constants as database connection parameters: DB_SERVER, DB_USERNAME, DB_PASSWORD and DB_DATASET. It also allows you to obtain user configuration options via a global function getUserOption(). Your webconnect.config.php file might look a little like this:
<?php // webconnect.config.php
require_once('hostAppInit.php');
Netfira::$syncPassword = getUserOption('netfira_sync_password');
Netfira::$dbType = Netfira::DB_TYPE_MYSQL;
Netfira::$dbHost = DB_SERVER;
Netfira::$dbUser = DB_USERNAME;
Netfira::$dbPass = DB_PASSWORD;
Netfira::$dbData = DB_DATASET;
Netfira::$fileStore = 'wc_files';
You'll notice we've invoked the host system's initialisation script with a require_once() call. This is to expose its constants and methods. Most e-commerce systems have a fairly straightforward way of achieving this – usually by including a single file.
There's also another configuration defined: Netfira::$fileStore, which is where WebConnect will store uploaded images and product attachments. If something doesn't need to be user-configurable, you might as well hard-code it.
These are the main WebConnect configuration variables, but there are several more you can set to make WebConnect behave the way you want. They are listed in the WebConnect Developers Reference.
It's fairly inevitable that you'll need to create an interface in your host system for site admins to configure WebConnect. At the very minimum, they will need to be able to set a sync password. In most cases, you'll want to give them some additional control. For example, in our osCommerce and Zen Cart plugins, we allow users to choose which types of product information will be syncrhonised.
An admin interface also gives administrators a place to pair or replicate records, which is important if the data exists in both the accounting system and the website before WebConnect is added.
Fortunately, we've created these interfaces for you and included them in WebConnect, so providing them is quite easy. We'll discuss pairing in the next section.
In a perfect world, every product would have a globally unique identifier that every computer system used to refer to it. Sadly, that's not the case, and product identifiers from WebConnect are unlikely to match identifiers in your e-commerce system. To accomodate this, WebConnect has pairing.
Simply put, pairing lets you match up any single record from WebConnect with one or more records in the host system. Pairing isn't limited to products; you can also pair buyer records from WebConnect with existing customer records.
How you pair records is entirely up to you. If your system allows, you can bypass pairing completely and feed your application directly from the WebConnect data tables. You can also pair automatically, if you're confident product names, or some other property, will be unique and identical in both systems.
Neither are likely to be acceptable arrangements, so for the real world, we have a user interface that provides suggestions to the administrator based on pre-determined matching criteria, and allows them to match products and buyers up with complete confidence that a computer isn't making all the wrong decisions for them. We also have a replication interface that allows them to choose which records from WebConnect to have replicated into the host system.
The interfaces are pure JavaScript libraries that turn an empty <div/> or other container into a complete application. They render semantically-structured DOM elements that you can style to perfectly fit the look and feel of your application. Or, you can use the included default stylesheets, which look pretty good out of the box.
First, to pair its records with records in the host system, WebConnect needs to know what's in the host system. You can provide this information to WebConnect by giving it some functions to call, which return data from your e-commerce app. We call them data providers.
Data providers are simply functions that return a small amount of data from every record in a data set. Here's what a typical data provider for product info might look like:
function productProvider()
{
$ret = array();
$result = mysql_query("SELECT * FROM products");
while($row = mysql_fetch_assoc($result))
{
$ret[$row['id']] = array(
'part' => $row['part_number'],
'description' = $row['product_name']
);
}
return $ret;
}
Providers return an associative array, in which keys are the records' unique identifiers, and values are associative arrays containing the relevant fields
For products, those fields are:
part (the product's part or model number), anddescription (the product's name or short description).For buyers, they are:
name (the buyer's name), andemail (the buyer's email address).Like event handlers, providers also need to be registered. To register the productProvider function as the data provider for products, add this line to your webconnect.config.php file:
Netfira::$pairing->setProductProvider('productProvider');
The procedure for providing buyers is exactly the same, except we need the name and email fields, and the function should be registered with the Netfira::$pairing->setBuyerProvider() method.
Let's get the product pairing interface onto an HTML page so the adminstrator can access it. Create a new PHP page with a respectable HTML structure, and add this to the document <head/> (or anywhere before the area you want to display the interface):
<script src="webconnect.php?res=productPairing.js&pw=Passw0rd"></script>
webconnect.php only serves resources to authenticated clients, but we don't really want our password in the markup! As an alternative, WebConnect provides cookie-based authentication. To create an authenticated session, insert this code at the very top of your page:
<?php Netfira::authoriseSession(); ?>
This code sends a cookie to the browser that it can use to request subsequent WebConnect resources. Now you can drop the &pw= part of your script source so it simply reads:
<script src="webconnect.php?res=productPairing.js"></script>
Much better. We'll assume the presence of the WebConnect auth cookie in subsequent examples. If you prefer to avoid cookies, just keep using the &pw= component instead.
If you want to use the default style (and it's probably not a bad idea when you're starting off), include this line in your document <head/>:
<link rel="stylesheet" href="webconnect.php?res=productPairing.css"/>
OK, now we have our arsenal, we can start shooting. Add a <div/> to your page with a unique id attribute:
<div id="pairing_container"></div>
This will be the container for our interface. Now to render the interface, add a <script/> block below:
<script>
nf.productPairing('pairing_container').init();
</script>
That's it. Load the page in your browser and watch the interface come to life.
Hint: it'll look pretty sparse if there's no data to play with, so refer to the TestCentre example above to push a few products up to play with. We'll cover some more sophistacated test scripts a little later on.
Replication is the counterpart to pairing. When your store owners have finished pairing up all their products, there might be a couple in their accounting system that aren't online yet. We'll call these "new" products.
One way to deal with new products is to have a switch in your administration interface that says "when I receive a new product, copy it straight to the online store". In fact, that's exactly what the handler in our first example does. But what if the store owner wants to pick and choose which products appear online? This is a very common requirement, and WebConnect provides an interface that allows store owners to do that.
The replication interface shows a list of unpaired records, and puts a "create" link next to each record. It also puts a "create all" link above the list, which is handy when an entire product line has just been added.
Embedding the replication API requires the same steps as embedding the pairing API. Just change all instances of productPairing to productReplication.
But hang on a minute – how does WebConnect know how to create products and customers in YOUR system?
It doesn't. For replication to work, you'll need to create another event handler:
function onCreateProduct($data)
{
$query = sprintf(
"INSERT INTO products (`product_name`, `unit_price`, `stock`) VALUES('%s', %s, %s)",
mysql_real_escape_string($data->description),
$data->unitPrice,
$data->stock
);
mysql_query($query);
$product_id = mysql_insert_id();
$data->pairWith($product_id);
}
Netfira::addEventHandler(NF_CREATE_PRODUCT, 'onCreateProduct');
Handlers for the NF_CREATE_PRODUCT and NF_CREATE_BUYER events are required to pair the records they create with the source record, by calling the pairWith() method of the first argument. Incidentally, this is the only time an event handler is ever required to do anything.
Receiving Data
Now that we've covered configuration and pairing, we're ready to start processing incoming data.
WebConnect gives you the ultimate freedom with data handling, in that it doesn't require any action; it simply notifies you when information is available, and presents the information in an easy-to-read format.
As a result, there are literally unlimited ways data can be handled. A lot will depend on the requirements of your host application; what information it can and can't use, what kinds of products it's used to sell, whether it's meant for wholesale or resale... the list goes on.
The WebConnect philosophy is to provide and store everything, and allow the application to consume the information in whatever fashion it requires. WebConnect creates its own data tables in your database, and its own folder on your hard disk. It maintains a data set that is completely independent of the data in your host system. This gives you the massive benefit of having a constant reference set that you can use to compare your system's data with the data in the store owner's accounting system.
WebConnect provides a comprehensive API for interacting with its data tables, so you'll never need to roll around in the mud with any SQL queries. Rather than covering the entire API in this guide, we'll focus on the parts relevant to what we're trying to achieve. For a more in-depth look at the WebConnect API, please refer to the WebConnect Developers Reference.
In our last example, we demonstrated receiving product information and copying it to the host system as a new product. But what if the product already exists?
A more realistic way to handle the NF_RECEIVE_PRODUCT event is to find out which product in the host system it's paired with, if any, and then apply updates. For this purpose, WebConnect provides the externalId() method. Consider this function as a handler for NF_RECEIVE_PRODUCT:
function onReceiveProduct(NFRecord $data)
{
$product_id = $data->externalId();
if($product_id !== null)
{
$query = sprintf(
"UPDATE products SET price = %s, stock = %s WHERE `id` = %s LIMIT 1",
$data->unitPrice,
$data->stock,
$product_id
);
mysql_query($query);
}
}
Notice that we've added the type hint NFRecord to the $data argument. If you're using a PHP-aware IDE like Netbeans or Eclipse, this can help by suggesting properties and methods as you type. WebConnect always provides data records to event handlers as instances of NFRecord.
Next, we've use the record's externalId() method to obtain the ID of the product it's paired with. If the record isn't paired, externalId() will return null.
Once we know the record is paired, we can use the external ID to update the host system. Whether you use a simple SQL query as we've shown here, or a more complex data abstraction system, the externalId() method is the key to matching data from WebConnect with data in the host system.
It's worth mentioning at this point that the external ID doesn't have to be numeric. In fact, it can be any string up to 111 bytes long.
If you're confident you want every product pushed straight into the host system, you can bypass the manual replication interface and create new products automatically as you receive them. Let's augment the last example with some extra code that looks after products that aren't paired:
function onReceiveProduct(NFRecord $data)
{
$product_id = $data->externalId();
if($product_id !== null)
{
$query = sprintf(
"UPDATE products SET price = %s, stock = %s WHERE `id` = %s LIMIT 1",
$data->unitPrice,
$data->stock,
$product_id
);
mysql_query($query);
}
else
{
$query = sprintf(
"INSERT INTO products (product_name, price, stock) VALUES('%s', %s, %s)",
mysql_real_escape_string($data->description),
$data->unitPrice,
$data->stock
);
mysql_query($query);
$product_id = mysql_insert_id();
$data->pairWith($product_id);
}
}
Now we're ready for any product, whether it's paired or not.
If you're not employing automatic replication, you may want to notify store owners when new products arrive.
If we replaced the else block in the example above with code to send an email to the store owner, we may experience an undesired side effect. Every time the product is updated, the owner will receive an email. That's no good.
You could keep track of which products you've sent emails about, but WebConnect has a neater trick up its sleeves, and it involves a new event: NF_WILL_RECEIVE_PRODUCT.
With the exception of CREATE events, WebConnect actually raises two events for every change; one before it makes changes to its internal data tables (with the NF_WILL_ prefix), and one after changes are written (with the NF_DID_ prefix). NF_RECEIVE_PRODUCT is actually an alias for NF_DID_RECEIVE_PRODUCT.
Still with me? Great! By handling the NF_WILL_RECEIVE_PRODUCT event, we can test the incoming data to see if the record exists in the database before it's written. You can test if a record exists by calling its exists() method. Let's modify our event handler to send a notification when a new product arrives:
function onReceiveProduct(NFRecord $data)
{
$product_id = $data->externalId();
if($product_id !== null)
{
$query = sprintf(
"UPDATE products SET price = %s, stock = %s WHERE `id` = %s LIMIT 1",
$data->unitPrice,
$data->stock,
$product_id
);
mysql_query($query);
}
else if(!$data->exists())
{
$to = STORE_OWNER_EMAIL;
$subject = 'A new product has arrived';
$message = "A new product ($data->part) is ready to be paired or replicated.";
mail($to, $subject, $message);
}
}
Netfira::addEventHandler(NF_WILL_RECEIVE_PRODUCT, 'onReceiveProduct');
Sometimes you can receive fifty products at once, so an even better approach would be to queue these notifications up somewhere, and send them all in a single email at the end of the update. WebConnect raises a NF_DID_MAKE_CHANGES event when it's finishing up a commit cycle that you can use to purge your notification queue. Have a look at the osCommerce/Zen Cart plugin for a nice example of this concept put into practise.
There's one catch with this arrangement; if the store owner responds to the notification by pairing the product with an existing one, we have to wait until another change is made in the accounting system before any data gets transferred. That's no good, is it? Enter the NF_PAIR_PRODUCT event!
When a product is paired, WebConnect raises a NF_PAIR_PRODUCT event. You can respond to this event however you like, but we think a good practice is to treat the product as if it's just been updated. Here's a nifty shortcut you can use:
function onPairProduct(NFRecord $data, $externalId)
{
onReceiveProduct($data);
}
Netfira::addEventHandler(NF_PAIR_PRODUCT, 'onPairProduct');
Since we haven't used the $externalId argument, we could leave it out of the declaration. We could even assign the onReceiveProduct function directly as the handler for NF_PAIR_PRODUCT, but having a separate handler is probably a bit clearer.
When a store owner deletes a product from their accounting system, WebConnect raises the NF_DELETE_PRODUCT event.
If your system links order history to records in its inventory, it mightn't be a great idea to follow WebConnect off the cliff. In a lot of cases, simply disabling the product is sufficient, but it's really up to the inner workings of your application.
Let's assume you want to respond to a product removal by disabling the product in the host system:
function onDeleteProduct(NFRecord $data)
{
$product_id = $data->externalId();
if($product_id !== null)
{
$query = "UPDATE products SET enabled = 0 WHERE `id` = $product_id LIMIT 1";
mysql_query($query);
}
}
Netfira::addEventHandler(NF_DELETE_PRODUCT);
If your store owner's accounting system has support for product images (and believe it or not, most of them do), WebConnect will store those images in your file system.
Remember in section 2, we defined this config variable:
Netfira::$fileStore = 'wc_files';
That's a relative path, so if webconnect.php were in the /var/www directory, WebConnect would store files in /var/www/wc_files. You can also specify an absolute path if you prefer. WebConnect will try to create the directory for you if it doesn't exist.
WebConnect treats images as individual records in is internal data tables, and stores relations between products and images in a separate table. This means products may have more than one image, and an image can be bound to more than one product.
When WebConnect receives an image, it raises the NF_RECEIVE_IMAGE event. When the image is added to a product, the NF_ADD_IMAGE_TO_PRODUCT event will be raised. Each event can be handled whatever way best suits your application, but we think a good practice is:
NF_RECEIVE_IMAGE event to prepare the image for display by creating thumbnails etc;NF_ADD_IMAGE_TO_PRODUCT event to adjust bindings.Let's say we want to create a thumbnail for each image we receive. We should use a separate directory, in case any of our file names conflict with existing images:
function onReceiveImage(NFRecord $record, &$data)
{
$thumbsDir = 'wc_thumbs';
if(!is_dir($thumbsDir))
{
mkdir($thumbsDir);
}
$thumbFile = $thumbsDir . '/' . $record->fileName;
if(is_file($thumbFile))
{
unlink($thumbFile);
}
$thumb = new Imagick();
$thumb->readImageBlob($data);
$thumb->thumbnailImage(80, 60);
$thumb->writeImage($thumbFile);
}
Netfira::addEventHandler(NF_RECEIVE_IMAGE, 'onReceiveImage');
One important difference between this and other event handlers, is that the second argument is prefixed with an ampersand, indicating it is passed by reference. This ensures that only one copy of the image need be stored in memory.
Here, we've used the Imagick extension to generate a thumbnail. You may well have your own method of creating thumbnails, andif so, you're perfectly welcome to use it. We provide a reference to the image binary as a convenience, since it's already in memory to begin with. If you need to read the file from the disk, you can access it at the path returned by the path() method of the hander's first argument.
Here's an example that uses an external method makeThumbFromFile($filePath) to create a thumbnail:
function onReceiveImage(NFRecord $record, &$data)
{
makeThumbFromFile($record->path());
}
Adding images to products is easy:
function onAddProductToImage(NFRecord $product, NFRecord $image)
{
$product_id = $product->externalId();
if($product_id !== null)
{
$query = sprintf(
"REPLACE INTO products_to_images (product_id, image_path) VALUES(%s, '%s')",
$product_id,
mysql_real_escape_string($image->path())
);
mysql_query($query);
}
}
Netfira::addEventHandler(NF_ADD_PRODUCT_TO_IMAGE, 'onAddProductToImage');
Here, we're updating the products_to_images table, which has two columns, both part of its primary key. If your system only allows one image per product, your handler will probably be much simpler.
Did you spot the problem in the last example? Yep, the product has to be paired.
If your application relies on manual pairing, it could be hours between the time we receive the product, and the time the store owner gets around to pairing it. The image, on the other hand, is likely to come much sooner. We have a race condition!
The solution is actually fairly simple. If you recall the previous section on products, we used the NF_PAIR_PRODUCT event to help our host system record "catch up" with the data in WebConnect. Here's the handler again, with some extra code to catch up on image links (I've changed the name of the first argument to $product for clarity):
function onPairProduct(NFRecord $product, $externalId)
{
onReceiveProduct($product);
$images = $product->getImages();
foreach($images as $image)
{
onAddProductToImage($product, $image);
}
}
Netfira::addEventHandler(NF_PAIR_PRODUCT, 'onPairProduct');
You can add some similar code to the NF_CREATE_PRODUCT handler, in case the product is replicated rather than paired. It's important to remember that the NF_PAIR_PRODUCT event is only raised when a product is paired via the product pairing UI; it isn't raised by calls to a product's pairWith() method.
Images also have their own events for when they're removed. There's the NF_REMOVE_IMAGE_FROM_PRODUCT event, for when an image is unlinked from a product, and the NF_DELETE_IMAGE event for when an image is deleted completely.
Handling the NF_REMOVE_IMAGE_FROM_PRODUCT event is quite easy, and in a lot of cases, will be the exact opposite of the NF_ADD_IMAGE_TO_PRODUCT handler:
function onRemoveProductFromImage(NFRecord $product, NFRecord $image)
{
$product_id = $product->externalId();
if($product_id !== null)
{
$query = sprintf(
"DELETE FROM products_to_images WHERE product_id = %s AND image_path = '%s' LIMIT 1",
$product_id,
mysql_real_escape_string($image->path())
);
mysql_query($query);
}
}
Netfira::addEventHandler(NF_REMOVE_PRODUCT_FROM_IMAGE, 'onRemoveProductFromImage');
Handling the NF_DELETE_IMAGE event should also be the reverse of NF_RECEIVE_IMAGE. In our case, we just need to delete the thumbnail we created (OK we don't HAVE to, but let's be neat):
function onDeleteImage(NFRecord $image)
{
$thumbPath = 'wc_thumbs/' . $image->fileName;
if(is_file($thumbPath))
{
unlink($thumbPath);
}
}
Netfira::addEventHandler(NF_DELETE_IMAGE, 'onDeleteImage');
WebConnect comes with two ways to categorise products. The configuration of the store owner's computer will determine which are used.
The first is the category property of each product. Somewhat like a tag, you can use this property to organise products or filter them in searches.
The second, more sophisticated feature, is the catalogue tree. It's a simple hierarchic data set with all the flexibility of a typical file/folder structure; any category may contain one or more sub-categories and/or products. Products may also belong to more than one category.
Categories come with many of the same events as products and images; there's a NF_RECEIVE_CATEGORY event for new or modified categories, NF_DELETE_CATEGORY for when a category is deleted, and events for adding and removing products from categories. Categories can also be paired with categories in your host system.
How we handle categories, however, is typically quite different. WebConnect doesn't offer pairing and replication interfaces for categories, because it's something we should be able to automate relatively easily.
Again, WebConnect doesn't impose any rules – the data comes and the events are raised. Pragmatically, most systems will have similar responses, so let's look at a typical one:
function onReceiveCategory(NFRecord $data)
{
$category_id = $data->externalId();
$parentCategory = $data->parent();
$parent_id = $parentCategory
? $parentCategory->externalId()
: 0;
if($category_id !== null)
{
$query = sprintf(
"UPDATE categories SET category_name = '%s', parent_id = %s WHERE category_id = %s LIMIT 1",
mysql_real_escape_string($data->name),
$parent_id,
$category_id
);
mysql_query($query);
}
else
{
$query = sprintf(
"INSERT INTO categories (category_name, parent_id) VALUES('%s', %s)",
mysql_real_escape_string($data->name),
$parent_id
);
mysql_query($query);
$category_id = mysql_insert_id();
$data->pairWith($category_id);
}
}
In this handler, we've introduced the parent() method, which is a special NFRecord method meant just for categories. It returns the parent category, or null if called from a top-level category.
This handler does a good job of adding new products and updating existing ones. But what if there's already a catalogue tree before WebConnect comes along?
For most systems, catalogue trees can be paired based on their name. For example, "Fruit" in the accounting system will match "Fruit" on the website, and "Fruit > Citrus" will match "Fruit > Citrus". Tracing a catalogue leaf back to the root, if the names match up, we can safely assume they're equivalents.
Let's modify the else block of the previous example so that we look for a matching category before creating a new one:
// ...
else
{
$query = sprintf(
"SELECT category_id FROM categories WHERE " .
"LOWER(category_name) = '%s' AND parent_id = %s LIMIT 1",
mysql_real_escape_string(strtolower($data->name)),
$parent_id
);
$result = mysql_query($query);
if(mysql_num_rows($result))
{
$category_id = mysql_result($result, 0);
}
else
{
$query = sprintf(
"INSERT INTO categories (category_name, parent_id) VALUES('%s', %s)",
mysql_real_escape_string($data->name),
$parent_id
);
mysql_query($query);
$category_id = mysql_insert_id();
}
$data->pairWith($category_id);
}
}
We already know what the category's parent ID will be, so all we have to do is look for a category with the same parent ID and name, and we can go ahead and pair it.
When products are added to, or removed from, categories, the NF_ADD_PRODUCT_TO_CATEGORY and NF_REMOVE_PRODUCT_FROM_CATEGORY events are raised. They're quite similar to the events raised for linking and un-linking products and images (in fact, all link/un-link events in WebConnect follow the same pattern).
Here's a typical handler for NF_ADD_PRODUCT_TO_CATEGORY:
function onAddProductToCategory(NFRecord $product, NFRecord $category)
{
$product_id = $product->externalId();
if($product_id !== null)
{
$category_id = $category->externalId();
$query = "REPLACE INTO products_to_categories " .
"(product_id, category_id) VALUES($product_id, $category_id)";
mysql_query($query);
}
}
Netfira::addEventHandler(NF_ADD_PRODUCT_TO_CATEGORY, 'onAddProductToCategory');
Only the product record needs a pairing check; categories are paired automatically, so we know there will be an external ID.
We face the same race condition here that we faced with linking images to products; that is, if this event occurs before the product is paired, the product won't find its way to its category. Let's augment our NF_PAIR_PRODUCT handler again:
function onPairProduct(NFRecord $product, $externalId)
{
onReceiveProduct($product);
$images = $product->getImages();
foreach($images as $image)
{
onAddProductToImage($product, $image);
}
$categories = $product->getCategories();
foreach($categories as $category)
{
onAddProductToCategory($product, $category);
}
}
Netfira::addEventHandler(NF_PAIR_PRODUCT, 'onPairProduct');
Just like our handler for removing images from products, the handler for removing products from categories will be the reverse of the one for adding them:
function onRemoveProductFromCategory(NFRecord $product, NFRecord $category)
{
$product_id = $product->externalId();
if($product_id !== null)
{
$category_id = $category->externalId();
$query = "DELETE FROM products_to_categories " .
"WHERE product_id = $product_id AND category_id = $category_id LIMIT 1";
mysql_query($query);
}
}
Netfira::addEventHandler(NF_REMOVE_PRODUCT_FROM_CATEGORY, 'onRemoveProductFromCategory');
In the last section, we've covered the basics of receiving product, image and catalogue information, and merging it into a host e-commerce system.
WebConnect is capable of transfering much more information than we've covered in this section, including many other product attributes, product attachments, buyer information and more. We'll look at buyer information briefly in the next section, but for a more in-depth look at the full range of data WebConnect can transfer, please refer to the WebConnect Developers Reference.
Sending Orders
In the last section, we looked at how to receive information from WebConnect and merge it into a host e-commerce system.
The other half of WebConnect's role is taking orders placed in the host system and sending them back to the store owner's PC, where they're converted into sales orders in the owner's accounting system.
Accounting systems like to tie sales orders to customers, so before we get into the details of passing orders to WebConnect, let's take a quick look at how WebConnect handles customer records.
In Netfira software, customers, or clients, are referred to as buyers.
Buyer information is sent to online stores in exactly the same way as product information. Additions or changes to the accounting system's customer list will raise NF_RECEIVE_BUYER events; deleting them will raise NF_DELETE_BUYER events.
The major difference is how the events are typically handled. There doesn't need to be as much continuity between records in the accounting system and the e-commerce system; in fact, most merchants would prefer they stay separate. For example, if a customer changes their email address or phone number for their account in the online store, it probably wouldn't be wise to overwrite that information with data from the accounting system.
That's not to say they shouldn't be paired up; in fact, many wholesalers and specialist retailers will only accept orders from customers with accounts in their accounting system, which means accounts need to be pair before orders can successfully be placed.
Just as WebConnect provides interfaces for product pairing and replication, the same interfaces can be used to pair WebConnect buyer records with existing customer records in the online store, and to replicate buyer records if they can't be paired.
The buyer pairing and replication interfaces are exact copies of the product pairing and replication interfaces. They have the same configuration options, and are embedded in the same way, except that instances of product are replaced with buyer.
Like product pairing, buyer pairing requires a provider to shed some light on what's in the host system's customer list. Here's a typical buyer provider:
function buyerProvider()
{
$ret = array();
$result = mysql_query("SELECT * FROM customers");
while($row = mysql_fetch_assoc($result))
{
$ret[$row['id']] = array(
'name' => $row['full_name'],
'email' = $row['email_address']
);
}
return $ret;
}
Netfira::$pairing->setBuyerProvider('buyerProvider');
If you refer to the product provider in the last section on receiving data, you'll see there's almost no difference between it and this example; we've just changed a couple of table and field names.
The only other thing you'll need to complete your buyer pairing and replication interfaces is a handler for NF_CREATE_BUYER, which is raised when the store owner clicks the "create" link next to a buyer record. Just like the handler for NF_CREATE_PRODUCT, our handler must pair the record it creates with the supplied buyer record using the pairWith() method.
Since your NF_CREATE_BUYER handler is creating a new user account, it may be a good place to generate a password and notify the user via email. Let's give it a shot:
function onCreateBuyer(NFRecord $buyer)
{
$password = dechex(mt_rand(0x10000000, 0xFFFFFFFF));
$query = sprint(
"INSERT INTO customers (full_name, email_address, password) " .
"VALUES('%s', '%s', '%s')",
mysql_real_escape_string($buyer->name),
mysql_real_escape_string($buyer->email),
md5($password)
);
mysql_query($query);
$customer_id = mysql_insert_id();
$buyer->pairWith($customer_id);
$to = sprintf('%s <%s>', $buyer->name, $buyer->email);
$subject = "Welcome to " . STORE_NAME;
$message = <<<EOF
Dear $buyer->name;
Welcome to our online store! You can log in using your email address
and this password:
$password
We hope you enjoy shopping with us.
EOF;
mail($to, $subject, trim($message));
}
Netfira::addEventHandler(NF_CREATE_BUYER, 'onCreateBuyer');
We've used the NF_CREATE_BUYER event to generate a random password, create a new customer record, pair it with the WebConnect buyer record, and email the customer. Neat, huh?
For many systems, this will be the only buyer-related event you'll need to handle. You could use NF_PAIR_BUYER and NF_RECEIVE_BUYER to update information like billing addresses, but be careful – you don't want to overwrite changes made by the customer!
Now that we have our buyer records sorted out, we can start attaching them to orders.
For sending order information to the store owner's PC, WebConnect takes care of the communication side with some HTTP polling (also known as Comet or reverse AJAX). All you have to do is catch the order as it's placed, and feed it into the WebConnect API.
This means you'll need to "hook" into the host system. For some system, it's as easy as registering an event handler, just like the ones WebConnect uses. For other less sophisticated systems, you may need to alter, or even replace, files or functions in the application's core.
Let's have a look at how we can take orders from a host system and transfer them to WebConnect.
To set the scene, let's say we've hooked into the host system, so that when an order is placed, it calls the wc_import_order($order_id) function with the ID of the order. Now all we have to do is write the function. Start with an empty function declaration, and get some information about the order from the host system:
function wc_import_order($order_id)
{
$query = "SELECT * FROM orders WHERE order = $order_id LIMIT 1";
$result = mysql_query($query);
$details = mysql_fetch_assoc($result);
}
Next, create a new NFOrder object. This object will receive the order information and pass it on to WebConnect.
$order = new NFOrder();
So far, so good.
Now we'll bind the order to a buyer record by setting its nfCode value (an NF code is a buyer's unique ID in WebConnect).
$buyer = Netfira::$db->getInternalBuyer($details['customer_id']);
if($buyer)
{
$order['nfCode'] = $buyer->nfCode;
}
The Netfira::$db->getInternalBuyer() method compliments the externalId() method of NFRecord instances; it accepts the ID of a record in the host system, and returns the WebConnect to which it's paired. If it hasn't been paired, you'll get a return value of null instead.
Next we'll populate $order with some other order information, like shipping details, payment information and comments. If your system doesn't collect billing or delivery information, you can harvest fields from your $buyer object instead.
$order['name'] = $details['full_name'];
$order['email'] = $details['email_address'];
$order['comment'] = $details['comment'];
$order['shippingAddress1'] = $details['delivery_address_line_1'];
$order['shippingAddress2'] = $details['delivery_address_line_2'];
$order['shippingCity'] = $details['delivery_town'];
$order['shippingState'] = $details['delivery_state'];
$order['shippingPostCode'] = $details['delivery_post_code]';
$order['shippingCountry'] = $details['delivery_country_code'];
if($buyer)
{
$order['billingAddress1'] = $buyer->billingAddress1;
$order['billingAddress2'] = $buyer->billingAddress2;
$order['billingCity'] = $buyer->billingCity;
$order['billingState'] = $buyer->billingState;
$order['billingPostCode'] = $buyer->billingPostCode;
$order['billingCountry'] = $buyer->billingCountry;
}
There are a lot of other fields you can populare in $order that I've omitted for brevity; see the WebConnect Developers Reference for a complete list.
Now all we have to do is add line items to the order. Each line will need to be identified as a product from WebConnect's product list. You can use another reverse-lookup function to retrieve product records: Netfira::$db->getInternalProduct().
$query = "SELECT * FROM order_lines WHERE order_id = $order_id";
$result = mysql_query($query);
while($line = mysql_fetch_assoc($result)
{
$product = Netfira::$db->getInternalProduct($line['product_id']);
if($product)
{
$order->addLine($product, $line['quantity']);
}
}
Note that we're only adding products if WebConnect knows about them. Accounting systems can't process orders for items that aren't in their inventories.
Once we've added all our information and line items to the order, all we have to do is call the place() method to send the order to WebConnect.
$order->place();
Calling the place() method only puts the order in a local queue, so there's no significant impact to response time. The order will be transferred to the owner's computer by a separate thread.
Let's take a look at the entire wc_import_order() function. We'll move a couple of lines around for simplicity.
function wc_import_order($order_id)
{
$query = "SELECT * FROM orders WHERE order = $order_id LIMIT 1";
$result = mysql_query($query);
$details = mysql_fetch_assoc($result);
$order = new NFOrder();
$order['name'] = $details['full_name'];
$order['email'] = $details['email_address'];
$order['comment'] = $details['comment'];
$order['shippingAddress1'] = $details['delivery_address_line_1'];
$order['shippingAddress2'] = $details['delivery_address_line_2'];
$order['shippingCity'] = $details['delivery_town'];
$order['shippingState'] = $details['delivery_state'];
$order['shippingPostCode'] = $details['delivery_post_code]';
$order['shippingCountry'] = $details['delivery_country_code'];
$buyer = Netfira::$db->getInternalBuyer($details['customer_id']);
if($buyer)
{
$order['nfCode'] = $buyer->nfCode;
$order['billingAddress1'] = $buyer->billingAddress1;
$order['billingAddress2'] = $buyer->billingAddress2;
$order['billingCity'] = $buyer->billingCity;
$order['billingState'] = $buyer->billingState;
$order['billingPostCode'] = $buyer->billingPostCode;
$order['billingCountry'] = $buyer->billingCountry;
}
$query = "SELECT * FROM order_lines WHERE order_id = $order_id";
$result = mysql_query($query);
while($line = mysql_fetch_assoc($result)
{
$product = Netfira::$db->getInternalProduct($line['product_id']);
if($product)
{
$order->addLine($product, $line['quantity']);
}
}
$order->place();
}
We've now written all the code necessary to take a sale through the entire e-commerce cycle; from the seller first obtaining the item, advertising it, and receiving an order for it.
In the next section, we'll look at testing our software with WebConnect's powerful, easy-to-use testing facilities.
Testing
In the first section, we looked briefly at TestCentre and how it can simulate changes in an accounting system being sent to WebConnect. Let's take some time to explore some further testing with TestCentre.
Open TestCentre in a browser using the following URI:
http://localhost/webconnect.php?pw=Passw0rd&unit=test.html
Remember to change the path and password to match your staging server.
TestCentre consists of a toolbar and a simple three-column layout; your list of test scripts is on the left, test scripts source is in the middle, and results are displayed on the right.
WebConnect comes with a variety of test scripts you can use to simulate typical WebConnect operations, such as a first-time sync. You can import test scripts by pasting them individually into the script editor, or you can import an entire set of tests by clicking "Import" on the toolbar, and pasting a test set into the pop-up text area.
As you develop your plug-in, you'll want to test various aspects individually, so it's worth knowing how to write your own scripts. TestCentre scripts execute line by line, and each line starts with one of the following keywords:
# - Denotes a comment. Lines starting with the # character are ignored.UPDATE - Create or update a record.DELETE - Delete a record.ADD - Link two records together.REMOVE - Unlink two records from each other.COMMIT - Commit pending UPDATE, DELETE, ADD and REMOVE actions to the server.PURGE - Delete an entire set of records.ORDERS - Download pending orders.WAIT - Pause script execution for a fixed interval.Let's say you want to test a procedure that automatically replicates new products as they're received.
Set up a handler that looks something like this:
function onReceiveProduct(NFRecord $product)
{
$product_id = $product->externalId();
if(!$product_id)
{
$query = sprintf(
"INSERT INTO products (product_name, price, stock_level) VALUES('%s', %s, %s)",
mysql_real_escape_string($product->description),
$product->unitPrice,
$product->stock
);
mysql_query($query);
return mysql_insert_id();
}
}
Netfira::addEventHandler(NF_RECEIVE_PRODUCT, 'onReceiveProduct');
Create a new test in TestCentre and call it "New Product Test".
Now add the following commands:
# Clear the products list: PURGE Products # Add a new product: UPDATE Product gsapple description = Granny Smith Apple unitPrice = 1.1 stock = 50 # Save the changes COMMIT
The PURGE command deletes every product in the list, as well as all of their relational records (links to other records, such as images and categories). It also causes WebConnect to raise the NF_PURGE_PRODUCTS event, which can be used to clear out the host system's inventory if that is the desired effect. Purges are discussed in the next section (Advanced Concepts).
The reason we're purging in this test is because we want to be able to run it over and over until we're happy with our code, and if we don't delete the product, it won't continuously be new.
Run the test, and you'll notice it fails. This is because our event handler has returned a non-null value, which is not allowed. Event handlers will also halt execution if they write any output (using echo, print_r etc). This can be quite useful during testing, as it allows us to inspect the inner workings of our procedures. In this case, we can see the ID of the product we've just created.
Make sure you remove any output or return code from your event handlers before publishing!
Now, comment out the return line and re-run the test. It should succeed.
TestCentre allows you to simulate every possible interaction between WebConnect and a merchant's PC, including adding images to products.
Images are special types of records that reference binaries in the file system. To send those binaries from TestCentre, we need to encode them as base64.
# Add a new image: UPDATE Image apple.gif R0lGODlhZABVAMQAAE9wGNPgqObs1bDEcmKFKXaWOKe/aIelSLjJgJm1WJyzaT5fC2qLL4SaTpSu V3uZPo6mWFt8I6G6Xm2QMY2qTJOwT3CPOIGhQmaNK3ydO6zBgiQnA3KTM09JFcTTkv///yH5BAAA AAAALAAAAABkAFUAAAX/4CeOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwiBIEjp4kYsBsIo3EaMnY lBiq1+xSuzxCpT7qtqosI7Fc5RdcE3Oz3oBirjCf0d2kgB1za7IJVnpKEB0dFn9wb4t/enwrfoB4 VR0bh3RWmGOBmWQee49THpKMTBWnFAcKDRuWEA+pDrKmpJtjn6Eicom8vYmxwA2Vlw3FsBCYnJ5p AaG2paOAxhYNEYaH1AQMxdCdeGy7z8p1jb+v2QDXEdrryO63pVygQ3ekqN7jBtXr14cA/9RUpZl0 b8w8IOHQXCjIEN6+fukABKRDiJY9Twd7UEmY6Z7HfJrOTXtH0OIpiwZ//wjIE81ksJMXZ8Wb2fFi l4w5Bj3LsPDlx4HLgrrk9FNLMx5mNqHiuRCD055MnQx6ciZOmSs9iRaFczTnkzxYDzB9OjZq1ltU jVRV6nMrk644dHJcyqFs3QJ2ZSKDx3ZOVqhujXr9Go7u3cMTEq8jG/AvpmMCmwKuqxUYFlw3ptY7 xc7utsWdQ8NqLHm0acRil8KsBXdG2quGnYYGTbs2asWIGac2qxVjm9ctU+Vm96+4cduyRSfHfbsy vi04X6hVC1s43trHs0vEvhy5tryWQTpyDdxjtuTaF6Rfp3328MOB0ZK3WljWaeLG1etfD5A7e9Dg hSdIFdGxANwWseGX3/9+DLbnoHLblNXbgDfJQFV1ItXFXoMcdvggfufB19YbmEl3YXCZXMeANQt6 6OJ/MHon4WrPFajCVDvdt2GLw7zIoYIAqjiibzCcyJaKK+4YkYeW+BhjkCuWFl9rBn515AMhprdA k1t26eKPx0X52V3xIUAlJHIppCGQHHK5n5tO9pdkYnhBJt5bfeikJpL9vdlgk3C++OR3YtqpFxl5 XkXLNHMq6aeXX8YJ4W4NOWHjCWstGshwPKrHJaCRggljncb4RKQL9WjKaJ+dMhmqkpMyJaClRaYa TKMKdgiqqx4OSiqZRZWI6mYJWsBmpIH2+qGxERo6jrAtROITs0/+6Wn/qKLKSSizzq7mRa1nmIfl uKy2eu25koYZ61mBfHJpEQhaIetY1WL7an6+hkgpTO4mGu+8fOZqrb0fFlforx8NYmF9quyGa4wE K7vswYBR9K2/8t4asKMR85fvewVp0O/CLnl2rLnZFkwct80mLPIRJKtqsn8p46vyqHTmnJrFMLv2 r3X0nqzlzf5B2TK7erwLL4qTbTso0VC7t5xjIr9MwxHSFEszxDY/vbKMRyM9cswwzfywx1F/rTZq IY9H9qZaU+t12msbDWzDFv/2s8Pjnj23wX93t66syqzh897v1U231GCTFZ7bVyP+q9+BV173mgDf 07Pe5sQtt+WN2z31/917QUteyRdMTjnolw+eeR1Ko9o5wIIzrm3oretGtQY7uGEf0Jyqvd3HOIve HE90xC7d7MDXPvfqzo9OeuE8+K6a6tA/HH3tSM64l/K1PtY85rkzzn3iqQPcCELMp08+hN7hLr36 swjBNN85mx//+fynLxP4p3sJ+vTXvwL6zx2qMJ1G2ve+z5XPgLo7IO+isIvKDJA5D2TM/CqGDADe YCV+GR/LtMc/AgZIAx6MCwP75rQSQhBh3wsFCMXnPge6cIMRfEcKexeNUtUQezfsXg7ncKZHVHAV +HthC1WHQKvlAlMr1JcN89cYQjVRgU8kQQWlCMQfNqszDeDZDvlgvSAQUWxMI+ngGLM4BepUrYli vBgb50jHOtrxjnjMYxRCAAA7; # Send the image: COMMIT # Add the image to the product ADD Image apple.gif TO Product gsapple # Save the change: COMMIT
Instead of following the UPDATE command with property assignments (a = b), we've included an image of a green apple (albeit a low-res one), encoded in base64 and terminated by a semicolon (remember the semicolon – your test will fail if it's missing!). After you run the test, you can see the image by clicking the "preview" link in the results column. You'll also see its file size and MD5 hash.
WebConnect includes a handy base64 encoder for encoding images to use with TestCentre. You can access it from the command line with the following syntax:
php webconnect.php base64 apple.gif
The base64 blob will be displayed in the console, and you can copy and paste it into your test.
You'll notice another strange thing we've done here: we've committed the image before adding it to the product. Binary data is sent as a separate HTTP request after the record itself is committed, so if we don't commit it separately, it won't be available when the image is added to the product.
WebConnect uses HTTP long polling (also known as Comet or reverese AJAX) to retrieve orders from web servers. This means the merchant's PC will make an HTTP request to the server, and the server will wait until an order is available before it responds. After a certain amount of time, if no orders are placed, the request times out and an empty response is sent. The merchant's PC then makes a new request, and the process continues.
You can test your order hooks using TestCentre's ORDERS command. Set up a new test with the following command:
# Poll for orders ORDERS 30
This command will wait 30 seconds for an order to be placed. If an order is placed, TestCentre will confirm its receipt of the order with WebConnect, and log the confirmation in the results column. You can inspect the received order by clicking the "Order Data" link in the confirmation notice.
Please consult the WebConnect Developers Reference for further information about TestCentre.
You can enable WebConnect's logging by specifying a log file in your webconnect.config.php:
Netfira::$logFile = 'WebConnect.log';
WebConnect logs incoming requests, raised events and other information that can be handy when you're developing. The logs can get quite verbose, so you may want to set a limit to the log's file size:
Netfira::$logLimit = 0x10000; // = 64 Kb
If you omit the limit, your log file can reach megabytes very quickly, and can impact your server's performance. We strongly discourage the use of logging in production environments.
You can write your own information to the WebConnect log file by passing any number of arguments to the Netfira::log() method. When logging is disabled (ie no value for Netfira::$logFile), calls to Netfira::log() are discarded, so it's safe to leave trace code in your plugins.
Now that we've covered testing, you're ready to start writing your WebConnect plugin! If you'd like to strengthen your grasp on WebConnect development, read on through the final chapter on Advanced Concepts.
Advanced Concepts
In order to write a solid plugin, worthy of a wide public release, you'll want to polish it up as much as possible. Here are a few things you should look into.
Throughout our examples, we've looked at pairing products and buyers from WebConnect with records in the host system.
WebConnect supports one-to-many pairing, whereby a product or buyer from WebConnect can be paired with more than one record in the host system.
At first glance, this can seem absurd, but there are plenty of realistic scenarios in which this can occur. For example:
If you're confident this won't occur in your system, you can probably skip this section, but for most plugins, it's a real issue that needs consideration.
Here's one of our previous examples, handling only one-to-one pairing:
function onReceiveProduct(NFRecord $data)
{
$product_id = $data->externalId();
if($product_id !== null)
{
$query = sprintf(
"UPDATE products SET price = %s, stock = %s WHERE `id` = %s LIMIT 1",
$data->unitPrice,
$data->stock,
$product_id
);
mysql_query($query);
}
}
If the product represented by $data was paired with multiple records in the host system, only one would be updated, because the externalId() method only returns the ID of the first paired record.
Here's the same handler with support for one-to-many pairing:
function onReceiveProduct(NFRecord $data)
{
$product_ids = $data->externalIds();
foreach($product_ids as $product_id)
{
$query = sprintf(
"UPDATE products SET price = %s, stock = %s WHERE `id` = %s LIMIT 1",
$data->unitPrice,
$data->stock,
$product_id
);
mysql_query($query);
}
}
There's almost no difference! We've just switched to the externalIds() method (note the plural), which returns all external IDs paired with the record, in an array. If the record isn't paired, externalIds() returns an empty array, so it's still safe to use in a foreach() block.
Some merchants (mainly wholesalers) restrict which buyers can buy which products. If your plugin is required to support such restrictions, WebConnect can help.
Buyers whose buying privileges are restricted will have their restrictProducts flag set. Here's how you can enforce product restrictions on a product listing page:
$buyer = Netfira::$db->getInternalBuyer($customer_id);
if(!$buyer)
{
// show nothing.
}
else if(!$buyer->restrictProducts)
{
// show everything.
}
else
{
// enforce restrictions:
$products = $buyer->getProducts();
foreach($products as $product)
{
// display product
}
}
If your system supports restricting buyers to specific products, you can register handlers for NF_ADD_PRODUCT_TO_BUYER and NF_REMOVE_PRODUCT_FROM_BUYER to keep your system up to date.
function onAddProductToBuyer(NFRecord $product, NFRecord $buyer)
{
$product_ids = $product->externalIds();
$customer_ids = $buyer->externalIds();
foreach($product_ids as $product_id)
{
foreach($customer_ids as $customer_id)
{
$query = "REPLACE INTO products_to_customers
(product_id, customer_id) VALUES($product_id, $customer_id)";
mysql_query($query);
}
}
}
Netfira::addEventHandler(NF_ADD_PRODUCT_TO_BUYER, 'onAddProductToBuyer');
In addition to restricting buyers to specific products, WebConnect can also provide buyer-specific product information, including custom pricing, tax and stock availability.
Because of the varying levels of complexity involved in calculating pricing in various accounting packages, WebConnect doesn't store any custom product information online. Instead, WebConnect obtains quotes from the store owner's computer, which are calculated in real time by the attached accounting software.
The particulars of implementing custom pricing and availability are beyond the scope of this document, and will be largely dependent on the nature of the application you're writing. Responses from the store owner's computer aren't likely to be fast enough to include custom pricing requests in page-rendering routines, but there are a number of ways you can achieve custom pricing, such as caching and latent page updates using ajax calls.
For more information about custom pricing and availability, please see the WebConnect Developers Reference.
When the store owner's computer polls your application for new orders, WebConnect will query the database at regular intervals (5 seconds by default). If your server is running Memcached and you have the PHP Memcache or Memcached extensions, you can use shared memory to eliminate database polling, and increase the responsiveness of order polling.
To use shared memory for inter-thread communication in WebConnect, all you need to do is specify a unique Memcached key in your WebConnect configuration. For example:
Netfira::$memcachedKey = 'WebConnectOrderNotification';
That's it. WebConnect will take care of the rest.
If your server is running multiple stores (such as in a shared hosting environment), you'll need to make sure the key you specify is unique for each store.
If your store owner switches to a different accounting system, or their computer crashes and they lose all their data, they may send one or more "purge" commands to WebConnect, which will delete every record of a specific type. These commands can be simulated in TestCentre by specifying the type to purge, for example:
# Delete the entire catalogue tree PURGE Categories
When a data set is purged, and you have registered DELETE handlers for that type of record, the records will be deleted one-by-one, with a DELETE event raised for each record.
Separately, WebConnect will raise PURGE events which you can use to re-initialise data sets. For example, your handler for NF_PURGE_CATEGORIES could re-create the default set of categories so your site doesn't appear completely empty.
As with other WebConnect events, events prefixed with NF_WILL_ and NF_DID_ will be raised before and after the purge is processed.
The handling of purge events is too nuanced to give examples that will appeal en masse. If you're not sure, you probably don't need to handle purge events at all; but it's worth knowing they're available if you need them.
WebConnect can manage multiple stores from a single instance of webconnect.php.
To enable multiple stores, simple set a store ID in your WebConnect configuration:
Netfira::$storeId = get_store_id();
The $storeId variable can be any string up to 111 bytes.
This presents the issue that each store will have the same sync URL, but you can easily use query string variables or mod_rewrite trickery to give stores unique sync URLS.
Here's an example using the query string:
if(isset($_GET['storeId']) && is_numeric($_GET['storeId']))
{
$query = sprintf(
"SELECT storeId, password FROM stores WHERE storeId = %s LIMIT 1",
$_GET['storeId']
);
$result = mysql_query($query);
if($row = mysql_fetch_assoc($result))
{
Netfira::$storeId = $row['storeId'];
Netfira::$syncPassword = $row['password'];
}
}
if(!Netfira::$storeId)
die('Invalid or unknown store ID');
Then, store 123 could simply enter this URL into their Netfira desktop application:
http://yoursite.com.ex/webconnect.php?storeId=123
If you don't want to store your passwords in plain text, don't panic. In the example above, storing your passwords as MD5 hashes (32 hex digits, lower case) will also work.