Processing the incoming URL within our registry object
index.php
.htaccess file
Configuration file
What about e-commerce?
An e-commerce registry?
Summary
Products and Categories
What we need
Product information
Category information
Structuring content within our framework
Pages
Content
Versioning
Building products, categories, and content functionality into our framework
Database
Content
Content types
Content versions
Products
Categories
Pages within our framework
Model
View
Controller
Products
Model
View
Controller
Categories
Model
View
Controller
Some thoughts
Product and category images
Routing products and categories
Featured products
Embedding products
Summary
Product Variations and User Uploads
Giving users choice
Simple variants
How could this work?
Combinations of variants
How will this work?
High-level overview
Database structure
Template switching
Templates
A look back at simple variants
Giving users control
How to customize a product?
Uploads
Custom text
Maintaining uploads
Security considerations
Database changes
Extending our products table
Template switching
Shopping basket preparation
Stock control
Product variations
Product customizations
Basket templates
Product subtotals
Summary
Enhancing the User Experience
Juniper Theatricals
The importance of user experience
Search
Finding products
Search box
Controlling searches with the products controller
Search results
Improving searches
Filtering products
Product attributes
Filter options
Processing filter requests
Displaying filtered products
Improving product filtering
Providing wish lists
Creating the structure
Saving wishes
Wish-list controller
Add to wish list
Viewing a wish list
Controller changes
Wish-list view
Purchases
Gift purchases
Self purchases
Improving the wish list
Recommendations
Related products
Controlling the related products
Viewing the related products
E-mail recommendations
Help! It's out of stock!
Detecting stock levels
Changing our controller
Out of stock: a new template bit
Tell me when it is back in stock please!
Stock alerts database table
More controller changes
It is back!
Giving power to customers
Product ratings
Saving a rating
Viewing ratings
Product reviews
Processing reviews/comments
Displaying reviews/comments
Combining the two?
Any other experience improvements to consider?
Summary
The Shopping Basket
Shopping baskets
Our basket
Per-page basket
Considerations for our shopping basket
Creating a basket
When to build a user's basket
Basket database
Basket contents
Viewing the basket
checkBasket method
The controller
Adding products
An addProduct method
The controller
A note on etiquette
Adding customizable products
Changing our basket database
Viewing the basket
Changing the model
The controller
Adding product variants
A new database table
Model changes
The controller
Editing quantities
From visitor to a user
The transferToUser function
Performing the transfer
Cleaning the basket
Expired contents
Displaying the basket on every page
Functionality
Summary
The Checkout and Order Process
Some examples
Amazon
Limitations
Useful features
eBay
Interesting points of note
Play.com
Interesting points of note
The process
The basket
Voucher codes
Shipping method
An overview
Authentication
Why should we authenticate the user at this stage?
Login
Register
Do nothing
Delivery address
Payment method
Offline payment method
Off-site payment method
On-site payment method
Confirmation
Payment details
Payment made
Order processed
Other points of note
Summary
Shipping and Tax
Shipping
Shipping methods
Shipping costs
Product-based shipping costs
Weight-based shipping costs
To think about: location-based shipping costs
Shipping rules
Free shipping
Capped shipping
Tracking
Integrating shipping costs into the basket
Shipping methods and a default
Calculating shipping costs based on products
Calculating shipping costs based on product weights
Considering shipping rules, and adjusting prices accordingly
Tax
Separately calculating tax values
To think about: location-based tax costs
A look at our basket now
Summary
Discounts, Vouchers, and Referrals
Discount codes
Discount codes data
Discount codes database
Discount codes functionality
Reducing the number of codes available
Purchasable voucher codes
Existing functionality
Discount codes
Product variations
Required additional functionality
Referrals
Database changes
New table: referrers
Changes
Functionality
Checkout process consideration
Summary
Checkout
Order process review
Authentication
Delivery address
Payment method
Confirmation
Storing orders in the database
Orders table
Order statuses
Order items
Order item attributes
Payment methods
Summary
Taking Payment for Orders
Taking payment
Our payment system
Taking payment online
PayPal
The payment button
Processing payment to update the order
Direct with a credit/debit card
Storing card details
Not storing card details
Other payment gateways
Payment gateway tips
Taking payment offline
Summary
User Account Features
User account area
Changing details
Changing password
Changing default delivery address
Viewing orders
Listing orders
Query
Viewing an order
Order model
Cancelling an order
Order model additions
Controller code
Expansion
Summary
Administration
Dashboard
Products and categories
Products
Creating a product
Editing a product
Categories
Creating a category
Editing a category
Deleting a category
Orders and customers
Orders
Updating an order
Dispatch note
Refunds
Customers area
Listing customers
A customer's orders
Miscellaneous
Shipping
Creating a shipping method
Voucher codes
Creating a voucher code
Summary
Deploying, Security, and Maintenance
Deploying
Hosting accounts and domain names
Hosting providers
Domain name registrars
Manual deployment
Setting up the database
Uploading our store
Settings
Automated deployment
Security
Server security
Software
Securing the site with a firewall
Passwords
SSL/TLS
CAPTCHA
Maintenance
Backing up and restoring
Using cPanel
Using the command line (SSH)
Summary
Marketing, SEO, and Customer Retention
Marketing sites and stores powered by our framework (and other sites for that matter)
Online advertising
Buying advertising space
Pay-per-click advertisements
Advertisement networks provided by search engines
Newsletter advertising
A word of warning: search engine penalization
Newsletters
Marketing materials
Affiliate marketing
Social marketing
Viral marketing
Twitter
RSS with FeedBurner
Search engine optimization
On-site SEO
Headings
Links
Up-to-date content
Meta tags
Sitemap and webmaster tools
Off-site SEO
Customer retention
Newsletters
Social features
Coupons and voucher codes
Summary
Interacting with Web Services
Google products
Adding the feed to the Google merchant center
Setting an update schedule
Creating the feed
Product feed controller
Other useful link
Alternative—Google Base Data API
Others
Google Analytics
Signing up
Tracking e-commerce
Add transaction
Add item
Track transaction
Further reading
Other services
Amazon
eBay.com
More to come
Summary
Downloadable Products
Extending products
Extending the payment and administration areas
Access database
Providing access
Rescinding access
Centralized download area
What else is needed?
Summary
Cookbook
Authentication reminders
Help! I forgot my password!
Generate the reset key, update the user record, and e-mail the customer
Reset the password
Help! I forgot my username!
E-mailing customers
Integrating Campaign Monitor
Integrating reCAPTCHA
On the registration page
When processing the registration
Tweeting about happy customers
Other uses
Summary
Index
Nội dung
Chapter 5 [ 123 ] Let's look at this as a step-by-step process: 1. We query the database for attribute types. 2. We cache the results of this query. 3. The cache is associated with a template tag. (This allows the template engine to generate a list of attribute types, and for each attribute type, it can build an empty list, surrounded by template tags, which will eventually contain the attribute values.) 4. We query the database for all attribute types, ordering by their own order. (Although the order is their order within their group, this does not matter, as we lter them out.) 5. We iterate through the results, putting each value into an array for its corresponding attribute type. 6. For each attribute type, we cache the array, and assign it to a template tag, allowing each group of values to populate the appropriate list for the attribute type. Our modied controller now looks like this, with our aforementioned six steps commented in for reference: private function generateFilterOptions() { // 1. Query the database for attribute types $attrTypesSQL = "SELECT reference, name FROM product_filter_attribute_types"; $this->registry->getObject('db')->executeQuery( $attrTypesSQL ); if( $this->registry->getObject('db')->numRows() != 0 ) { $attributeValues = array(); $attributeTypes = array(); while( $attributeTypeData = $this->registry-> getObject('db')->getRows() ) { $attributeValues[ $attributeTypeData['reference'] ] = array(); $attributeTypes[] = array( 'filter_attr_reference' => $attributeTypeData['reference'], 'filter_attr_name' => $attributeTypeData['name'] ); } // 2. cache the results of this query $attributeTypesCache = $this->registry->getObject('db')-> cacheData( $attributeTypes ); // 3. The cache is associated with a template tag $this->registry->getObject('template')->getPage()-> This material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Enhancing the User Experience [ 124 ] addTag( 'filter_attribute_types', array( 'DATA', $attributeTypesCache ) ); // 4. We query the database for all attribute types, // ordering by their own order $attrValuesSQL = "SELECT v.name AS attrName, t.reference AS attrType, v.ID AS attrID FROM product_filter_attribute_values v, product_filter_attribute_types t WHERE t.ID=v.attributeType ORDER BY v.order ASC"; $this->registry->getObject('db')->executeQuery( $attrValuesSQL ); if( $this->registry->getObject('db')->numRows() != 0 ) { // 5. We iterate through the results, putting each value into // an array for its corresponding attribute type. while( $attributeValueData = $this->registry->getObject('db')-> getRows() ) { $data = array(); $data['attribute_value'] = $attributeValueData['attrName']; $data['attribute_URL_extra'] = 'filter/' . $attributeValueData['attrType'] . '/' . $attributeValueData['attrID']; $attributeValues[ $attributeValueData['attrType'] ][] = $data; } } // 6. For each attribute type, we cache the array, and assign it // to a template tag, allowing each group of values to // populate the appropriate list for the attribute type. foreach( $attributeValues as $type => $data ) { //echo '<pre>' . print_r( $attributeValues, true ) . '</pre>'; $cache = $this->registry->getObject('db')->cacheData( $data ); $this->registry->getObject('template')->getPage()-> addPPTag( 'attribute_values_' . $type, array( 'DATA', $cache ) ); } } } This material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Chapter 5 [ 125 ] Processing filter requests With the relevant database structure in place, and functionality available for our customers to select attributes for which they wish to lter their product viewings, we need a method to process the request and actually lter the products listing. This involves iterating through the bits within the URL, and for every instance of filter found, storing the following two values. Once all bits of the URL have been processed, the saved bits should be processed to build a suitable query to lter the products. We will need some variables within our controller to store some of the data we will be processing. These would include: An array containing the lter attribute types, so we can pass the components of the URL to it in order to determine if the attribute value is from part of the products table itself, or if it is from an attribute association An array containing the lter attribute values, so when we nd an attribute type that refers to the products table, we can get the upper- and lower-bound values for this An array of pieces of SQL to search for attribute associations An array of pieces of SQL to search for attribute values within the products table A counter for the number of lters by association, as we will group this part of the search into a subquery, returning the results of a count, and we will know if we have a match if the count matches the number of conditions to the subquery These variables are displayed as follows: // Filter count: to count how many attributes by association // must match private $filterCount=0; // SQL statement parts where products are associated with // attributes private $filterAssociations = array(); // SQL statement parts where products are filtered by their own // direct properties i.e. price, weight. private $filterDirect = array(); // Array of filter attribute types private $filterTypes = array(); // Array of filter attribute values private $filterValues = array(); // our SQL statement for filtered products private $filterSQL = ''; • • • • • This material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Enhancing the User Experience [ 126 ] We now need a function to search through the URL and another function to add query pieces to our various arrays when it is passed the lter type and lter value once an occurrence of the word filter is found in the URL. So, rstly we'll see a function to go through the URL. /** * Generate an SQL statement for filtering products, based on URL * paramaters * @param array $bits the bits contained within the URL * @return void */ We rst get all of the attribute types available, and then we get all of the attribute values. private function filterProducts( $bits ) { // get our attribute types $attributeTypesSQL = "SELECT ID, reference, name, ProductContainedAttribute FROM product_filter_attribute_types "; $this->registry->getObject('db')->executeQuery( $attributeTypesSQL ); while( $type = $this->registry->getObject('db')->getRows() ) { $this->filterTypes[ $type['reference'] ] = array( 'ID' => $type['ID'], 'reference'=>$type['reference'], 'ProductContainedAttribute'=> $type['ProductContainedAttribute'] ); } // get our attribute values $attributeValuesSQL = "SELECT ID, name, lowerValue, upperValue FROM product_filter_attribute_values"; $this->registry->getObject('db')-> executeQuery( $attributeValuesSQL ); while( $value = $this->registry->getObject('db')->getRows() ) { $this->filterValues[ $value['ID'] ] = array( 'ID' => $value['ID'], 'name' => $value['name'], 'lowerValue' => $value['lowerValue'], 'upperValue' => $value['upperValue'] ); } This material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Chapter 5 [ 127 ] For each part of the URL, we go through and nd anything that relates to the lter functionality, which is of the format filter/attribute-type/attribute-value. // process the URL foreach( $bits as $position => $bit ) { // if we find filter in the URL if( $bit == 'filter' ) { // send the next two bits to the addToFilter method $this->addToFilter( $bits[ $position+1], $bits[ $position+2] ); } } We assume there are no lter requests being made, and set the basic lter query. Then we check if we have any lters that are not based on the product table values; if there are, we set the somethingToFilter variable, then we do the same for the lters based on the product table values. Each lter found adds additional restrictions to the basic lter SQL query. // assume no filter requests $somethingToFilter = false; // basic filter query $sql = "SELECT p.price AS product_price, v.name AS product_name, c.path AS product_path FROM content c, content_types t, content_versions v, content_types_products p WHERE v.ID=c.current_revision AND c.active=1 AND p.content_version=v.ID AND t.reference='product' AND c.type=t.ID "; if( !empty( $this->filterAssociations ) ) { // we have some filter requests $somethingToFilter = true; // build the query $sqla = " AND ( SELECT COUNT( * ) FROM product_filter_attribute_associations pfaa WHERE ( "; $assocs = implode( " AND ", $this->filterAssociations ); $sqla .= $assocs; $sqla .= " )AND pfaa.product = c.ID )={$this->filterCount}"; $sql .= $sqla; } if( !empty( $this->filterDirect ) ) { // we have some filter requests This material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 . and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Chapter 5 [ 1 25 ] Processing filter requests With the relevant database structure in place,. copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Chapter 5 [ 127 ] For each part of the URL, we go through and nd anything that relates. material is copyright and is licensed for the sole use by jackie tracey on 23rd February 2010 953 Quincy Drive, , Brick, , 08724 Enhancing the User Experience [ 124 ] addTag( 'filter_attribute_types',