'Customer',
'page callback' => 'drupal_get_form',
'page arguments' => array('ec_customer_ctypes_settings'),
'type' => MENU_NORMAL_ITEM,
'description' => 'Configure customer types and any other settings.',
'access callback' => 'user_access',
'access arguments' => array(EC_PERM_SETTINGS),
'file' => 'ec_customer.admin.inc',
);
$items['admin/config/store/customer/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/config/store/customer/types'] = array(
'title' => 'Types',
'type' => MENU_LOCAL_TASK,
'description' => 'Configure customer types and any other settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('ec_customer_ctypes_form'),
'access callback' => 'user_access',
'access arguments' => array(EC_PERM_SETTINGS),
'file' => 'ec_customer.admin.inc',
);
return $items;
}
/**
* Implements hook_theme().
*/
function ec_customer_theme() {
return array(
'ec_customer_address' => array(
'render element' => 'form',
'file' => 'ec_customer.theme.inc',
),
'ec_customer_ctypes_form' => array(
'render element' => 'form',
'file' => 'ec_customer.theme.inc',
),
'ec_mail_checkout_form' => array(
'render element' => 'form',
'file' => 'ec_customer.theme.inc',
),
'checkout_address' => array(
'render element' => 'form',
'template' => 'templates/checkout_address',
),
);
}
/**
* Implements hook_link().
*/
function ec_customer_link($type, $object = NULL) {
$l = array();
if ($type == 'ec_customer_list') {
$customer = & $object;
if ($link_view = ec_customer_links($customer, 'view')) {
$l['view'] = array(
'href' => $link_view[0],
'title' => t('View'),
);
}
if ($link_edit = ec_customer_links($customer, 'edit')) {
$l['edit'] = array(
'href' => $link_edit[0],
'title' => t('Edit'),
);
}
}
return $l;
}
/**
* Implements hook_form_alter().
*/
function ec_customer_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'ec_search_form') {
switch ($form['search_id']['#value']) {
case 'ec_store_transaction':
case 'ec_receipt':
$form['advanced']['fields']['keywords1']['name'] = array(
'#type' => 'textfield',
'#title' => t('Customer Name'),
'#default_value' => isset($form['#query']['name']) ? $form['#query']['name'] : '',
'#size' => 30,
'#description' => t('The name of the customer to search for'),
'#weight' => 0.001,
);
$form['advanced']['fields']['ctype'] = array(
'#type' => 'checkboxes',
'#title' => t('Customer Type'),
'#default_value' => isset($form['#query']['ctype']) ? $form['#query']['ctype'] : '',
'#options' => ec_customer_get_types('names'),
'#prefix' => '
',
'#suffix' => '
',
);
break;
}
}
}
/**
* Implements hook_views_api().
*/
function ec_customer_views_api() {
return array('api' => 2.0);
}
/**
* ec_customer_check_access()
*/
function ec_customer_check_access($type, $id = NULL) {
global $user;
if (user_access(EC_PERM_REPORT)) {
return TRUE;
}
$this_customer = ec_customer_get_customer();
if (empty($this_customer->ecid)) {
// This user is not a customer so don't check any further
return FALSE;
}
switch ($type) {
case 'user':
if (empty($id) && arg(0) == 'user' && is_numeric(arg(1))) {
$id = arg(1);
}
elseif (empty($id)) {
return FALSE;
}
if ($customer = ec_customer_get_by_uid($id)) {
if ($this_customer->ecid == $customer->ecid) {
return TRUE;
}
}
break;
case 'receipt':
if (empty($id)) {
// is not a valid receipt.
return FALSE;
}
if ($this_customer->ecid == $id->ecid) {
return TRUE;
}
$customer = ec_customer_get_customer($id->ecid);
break;
case 'transaction':
if (empty($id)) {
return FALSE;
}
if ($this_customer->ecid == $id->ecid) {
return TRUE;
}
$customer = ec_customer_get_customer($id->ecid);
break;
}
/**
* TODO: Add ability to check Cookies/Get/Session for token to see if
* they can access the current peice of information
*/
return FALSE;
}
/**
* Get Customer types and other information. The information is sourced
* via hook_customer_info.
*
* @param $op string
* Specifies the information to return.
* 'types' for the full set of customer types.
* 'type' for one customer type only (need $ctype parameter also).
* 'module' to find the module that implements a customer type (need $ctype parameter also).
* 'names' to get a descriptive name for all customer types.
* 'name' to get a descriptive name for one customer type.
*
* @param $ctype string
* A valid customer type name (like 'user', 'anonymous')
*
* @param $reset boolean
* If TRUE, rebuild the customer type data before returning information.
*
* @return
* Return data specified by $op
*
* TODO: all references to customer type like ->type should become ->ctype, since
* this is clearly the easiest way to describe it in docs, it should also be this way
* all through the code.
*/
function ec_customer_get_types($op = 'types', $ctype = NULL, $reset = FALSE) {
$_customer_types = & drupal_static(__FUNCTION__ . '__customer_types');
$_customer_names = & drupal_static(__FUNCTION__ . '__customer_names');
// Build up the initial data that defines the available customer types.
if ($reset || !isset($_customer_types)) {
$_customer_names = array();
$weights = variable_get('ec_customer_ctypes', array());
foreach (module_implements('customer_info') as $module) {
$path = drupal_get_path('module', $module);
$types = module_invoke($module, 'customer_info');
foreach ($types as $type => $info) {
$info = (object) $info;
$info->type = $type;
if (isset($weights[$type])) {
$info->weight = $weights[$type]['weight'];
}
else {
$info->weight = 0;
}
if (isset($info->file)) {
$info->include = $path . '/' . $info->file;
}
$_customer_types[$type] = $info;
}
}
drupal_alter('customer_info', $_customer_types);
uasort($_customer_types, 'ec_sort');
$_customer_names = array_map('_ec_customer_map_name', $_customer_types);
}
// Clarify the $ctype data as a simple string.
if (isset($ctype)) {
if (is_array($ctype)) {
$ctype = $ctype['type'];
}
elseif (is_object($ctype)) {
$ctype = $ctype->type;
}
if (!isset($_customer_types[$ctype])) {
return FALSE;
}
}
// Return the requested data.
switch ($op) {
case 'types':
return $_customer_types;
case 'type':
return $_customer_types[$ctype];
case 'module':
return $_customer_types[$ctype]->module;
case 'names':
return $_customer_names;
case 'name':
return $_customer_names[$ctype];
}
}
function _ec_customer_map_name($a) {
return $a->name;
}
/**
* Call ec_customer hooks
*
* @param $arg1
* Customer type
*
* @param $arg2
* Hook name, eg 'customer_get_id' or 'customer_get_address'
*
* @param ...
* Extra parameters that may be required by the hook.
*
* @return
* Whatever the invoked function returns.
*/
function ec_customer_invoke() {
$args = func_get_args();
$ctype = array_shift($args);
$hook = array_shift($args);
if ($function = ec_customer_get_function($ctype, $hook)) {
// We have a callable function.
return call_user_func_array($function, $args);
}
}
/**
* Call all ec_customer hooks
*/
function ec_customer_invoke_all() {
$ctypes = ec_customer_get_types();
$args = func_get_args();
$return = array();
$hook = array_shift($args);
foreach ($ctypes as $ctype => $info) {
if ($function = ec_customer_get_function($ctype, $hook, $info->module)) {
// We have a callable function.
$value = call_user_func_array($function, $args);
if (isset($value) && is_array($value)) {
$return = array_merge($return, $value);
}
elseif (isset($value)) {
$return[] = $value;
}
}
}
return $return;
}
/**
* Get the name of a function by passing the hook. If the function is not
* found in the ctype module, a common library will be invoked (if available).
*
* Background: To make the building of customer modules easy, the customer
* module provides some nice default common functions. This means that when
* creating new new customer module which are based
* upon standard types like 'user' the new module doesn't need to duplicate
* standard parts of the ec_customer interface. eg the implementation of
* hook_customer_get_by_uid() which will be the same for all customers
* module that implement the same type like 'user', by the module can still
* provide it's own version and the hook even thou the common interface
* provides this as well.
*
* At the moment only the 'user' customer type provides a common interface,
* but more can be added.
*
* These functions can be loaded into an include file called
* ec_customer_{type}.inc and all the hook are named as
* ec_customer_{type}_{hook}()
*
* @param $type
* This contains the type of customer interface which is being used.
*
* @param $hook
* The hook to check to see if this exists within this include.
*
* @param $module
* Allows the caller to specify the module so this function doesn't have to
* look it up.
*
* @return
* Returns a valid function that can be called or NULL
*/
function ec_customer_get_function($ctype, $hook, $module = FALSE) {
// Check the preferred function, usually one implemented by the ctype module
$type = ec_customer_get_types('type', $ctype);
if (!empty($type->include) && file_exists($type->include)) {
include_once DRUPAL_ROOT . '/' . $type->include;
}
$module = ($module) ? $module : ec_customer_get_types('module', $ctype);
$function = "{$module}_{$hook}";
if (function_exists($function)) {
return $function;
}
// Secondly check for a common interface function.
$function_common = "ec_customer_{$ctype}_{$hook}";
if (function_exists($function_common)) {
return $function_common;
}
// Unknown function, try to load the .inc file that provides the common
// functions for this ctype and then try again.
if (module_load_include('inc', 'ec_customer', 'ec_customer_' . $ctype) !== FALSE) {
if (function_exists($function_common)) {
return $function_common;
}
}
}
/**
* Get the customer information of the specified customer.
*
* @param $filter
* Can be an ecid (an ecommerce customer id). Or an array of filter values
* similar to how user_load() works.
*
* @return
* A customer object.
*/
function ec_customer_get_customer($filter = array()) {
// Standardize the filter.
if (is_object($filter)) {
$filter = (array) $filter;
}
elseif (is_string($filter)) {
$filter = array('ecid' => $filter);
}
// First check the ec_customer table.
if (!empty($filter) && ($customer = _ec_customer_find($filter))) {
return $customer;
}
// Next, use the ruling customer type to get a customer.
if (isset($filter['type']) && ec_customer_get_types('type', $filter['type'])) {
if ($return = ec_customer_invoke($filter['type'], 'customer_get_id', $filter)) {
return (object) $return;
}
}
// Having failed to find the customer yet, go through all the customer type providers.
$ctypes = ec_customer_get_types();
$customers = array();
// Ask each provider if they know the customer.
foreach ($ctypes as $ctype => $info) {
if ($customer = ec_customer_invoke($ctype, 'customer_get_id', $filter)) {
if ($return = _ec_customer_find($customer)) {
// Found the customer, return it.
return $return;
}
elseif (isset($customer)) {
// We've got a customer from a type provider, but we haven't encountered this
// customer before (ie. _ec_customer_find turned up blank).
// Make a note of the customer on an array.
$customers[] = (object) $customer;
}
}
}
// All else failed, so send the first customer (or FALSE) from our array of
// previously unknown customers.
return reset($customers);
}
/**
* This is an internal function. External callers should
* user ec_customer_get_customer(), which can also accept filters.
*
* @return
* An object representing the customer.
*/
function _ec_customer_find($filter) {
$filtered = FALSE;
$select = db_select('ec_customer', 'c')
->fields('c');
// Build the field names and values arrays for the sql.
$schema = drupal_get_schema('ec_customer');
foreach (array_keys($schema['fields']) as $field) {
if (isset($filter[$field])) {
$select->condition($field, $filter[$field], '=');
$filtered = TRUE;
}
}
if ($filtered) {
if ($customer = $select->execute()->fetchObject()) {
return $customer;
}
}
}
/**
* Return the customer information based upon the uid passed.
*/
function ec_customer_get_by_uid($uid) {
$ctypes = ec_customer_get_types();
$customers = array();
foreach ($ctypes as $ctype => $info) {
if (($customer = ec_customer_invoke($ctype, 'customer_get_by_uid', $uid)) && ($return = _ec_customer_find($customer))) {
return $return;
}
elseif (isset($customer)) {
$customers[] = (object) $customer;
}
}
$customer = reset($customers);
return $customer;
}
/**
* @todo Please document this function.
* @see http://drupal.org/node/1354
*/
function ec_customer_get_by_email($mail) {
$ctypes = ec_customer_get_types();
$customers = array();
foreach ($ctypes as $ctype => $info) {
if (($customer = ec_customer_invoke($ctype, 'customer_get_by_email', $mail)) && ($return = _ec_customer_find($customer))) {
return $return;
}
elseif (isset($customer)) {
$customers[] = (object) $customer;
}
}
$customer = reset($customers);
return $customer;
}
/**
* Insert the customer data into the database.
*/
function ec_customer_insert($customer) {
$customer->uid = ec_customer_get_uid($customer);
$customer->name = ec_customer_get_name($customer);
if (drupal_write_record('ec_customer', $customer)) {
$customer->token = drupal_get_token($customer->ecid . $customer->type . $customer->exid);
drupal_write_record('ec_customer', $customer, 'ecid');
ec_customer_invoke($customer->type, 'customer_insert', $customer);
return $customer;
}
}
/**
* Update an existing customer record. This maybe used for changing a
* customer from 1 customer type to another
*
* Updating a customer may make the user in accessible by existing tokens
* that are available.
*/
function ec_customer_update($customer) {
$customer->uid = ec_customer_get_uid($customer);
$customer->name = ec_customer_get_name($customer);
$customer->token = drupal_get_token($customer->ecid . $customer->type . $customer->exid); // Rebuild the token so that it apply again.
if (drupal_write_record('ec_customer', $customer, 'ecid')) {
ec_customer_invoke($customer->type, 'customer_update', $customer);
}
}
/**
* Get the customers user id. The id returned depends on the customer type.
*/
function ec_customer_get_uid($customer) {
$customer = ec_customer_get_customer($customer);
$uid = ec_customer_invoke($customer->type, 'customer_get_uid', $customer);
return isset($uid) ? $uid : 0;
}
/**
* Get the customers primary email address
*/
function ec_customer_get_email($customer) {
$customer = ec_customer_get_customer($customer);
return ec_customer_invoke($customer->type, 'customer_get_email', $customer);
}
/**
* Get stored information about the customer
* @todo Add in other information such as customer balance.
*/
function ec_customer_get_info($customer) {
$customer = ec_customer_get_customer($customer);
$cinfo = ec_customer_invoke($customer->type, 'customer_get_info', $customer);
return $cinfo;
}
/**
* Get customer name
*/
function ec_customer_get_name($customer) {
$customer = ec_customer_get_customer($customer);
return ec_customer_invoke($customer->type, 'customer_get_name', $customer);
}
/**
* Get stored addresses from customer database.
*
* Note: I have no idea why we're always calling:
* ec_customer_get_attr($txn->customer->type, 'store_addresses', TRUE)
* since this fucntion would be the perfect place to do this check.
*
* @param $customer
* A valid customer search array to allow the identification of the customer
* @param $aid
* An identifier of the address that is being searched for. If ommitted then all
* addresses will be returned.
*
* @return
* An array will be returned of the addresses for the customer specified. If there
* are no addresses then an empty array will be returned.
*/
function ec_customer_get_addresses($customer, $aid = NULL) {
$customer = ec_customer_get_customer($customer);
$addresses = ec_customer_invoke($customer->type, 'customer_get_address', $customer, $aid);
return $addresses ? $addresses : array();
}
/**
* Get different links that e-Commerce will require.
*/
function ec_customer_links($customer, $op, $query = NULL) {
$customer = ec_customer_get_customer($customer);
$goto = ec_customer_invoke($customer->type, 'customer_links', $customer, $op);
if (!isset($goto)) {
return;
}
elseif (is_array($goto) && empty($goto[1]) && isset($query)) {
$goto[] = $query;
}
elseif (is_array($goto) && isset($query)) {
if (isset($goto[1])) {
$goto[1] .= '&' . $query;
}
else {
$goto[1] = $query;
}
}
elseif (!is_array($goto) && isset($query)) {
$goto = array($goto, $query);
}
elseif (!is_array($goto)) {
$goto = array($goto);
}
return $goto;
}
/**
* Return Attributes about the customer type. There is a big question mark over
* the name of this function, since the only information it seems to be providing
* is about address handling.
*
* @param $ctype
* A customer type. (TODO: need link to documentation about customer types.)
*
* @param $attr
* An attribute. it corresponds to one of the values in the array that is
* passed back by hook_customer_info(). At the time of writing, the only
* attributes that ecommerce core requests via this function are:
*
* 'store_addresses'
* TRUE if a customer type can manage address forms and storage
* on it's own.
* 'add_address'
* TRUE if a customer type can manage multiple addresses for the
* user. This is a curious setting since, assumably, address.module
* can do this, but it doesn't return this setting in hook_customer_info().
*
* @param $default
* The function acts a little like variable_get, in that you can pass a default.
*
* TODO: make sure that calls for features of customer types don't set 'TRUE' as
* default. The customer type might not know anything about the 'store_addresses'
* attribute, yet 'TRUE' will assume it provides the feature. Eg. grep for:
* ec_customer_get_attr($txn->customer->type, 'store_addresses', TRUE)
* The $default parameter is dangerous and should be removed, instead customer
* type attributes should be initialized in a standard way so that the values
* returned from hook_customer_info are mainly overrides. END TODO.
*
* @return
* Returns a valid function that can be called or NULL
*
*/
function ec_customer_get_attr($ctype, $attr, $default = NULL) {
if ($info = ec_customer_get_types('type', $ctype)) {
if (isset($info->$attr)) {
return $info->$attr;
}
}
return $default;
}
/**
* Implements hook_ec_transaction_pre_save().
*/
function ec_customer_ec_transaction_pre_save(&$txn) {
if (empty($txn->ecid)) {
// First off we will check to see if a customer has been created.
if (empty($txn->customer->ecid)) {
$txn->customer = ec_customer_get_customer($txn->customer);
}
// If a customer has not been created then we need to create a new customer.
if (empty($txn->customer->ecid)) {
$customer = $txn->customer;
$customer->mail = $txn->mail;
$txn->customer = ec_customer_insert($customer);
}
$txn->ecid = $txn->customer->ecid;
}
}
/**
* Implements hook_ec_transaction_load().
*/
function ec_customer_ec_transaction_load(&$txn) {
if (empty($txn->customer) && $txn->ecid) {
$txn->customer = ec_customer_get_customer($txn->ecid);
}
}
/**
* Implements hook_checkout_info().
*/
function ec_customer_checkout_info() {
return array(
'customer' => array(
'name' => t('Customer'),
'description' => t('If required, display the billing and/or shipping address form(s) to the customer.'),
'module' => 'ec_customer',
'file' => 'ec_customer.checkout.inc',
'weight' => -7,
),
'email' => array(
'name' => t('EMail'),
'description' => t('If required, display the submit email form to the customer.'),
'module' => 'ec_customer_email',
'file' => 'ec_customer.checkout.inc',
'weight' => -8,
),
);
}
/**
* Customer List
*/
function ec_customer_list() {
$header = array(
array(
'data' => t('Customer Id'),
'field' => 'ec.ecid',
'sort' => 'ASC',
),
array(
'data' => t('Type'),
'field' => 'ec.type',
),
array('data' => t('Name')),
array('data' => ''),
);
$rows = array();
$result = db_select('ec_customer', 'ec')
->fields(array('ec' => array('*')))
->extend('PagerDefault')
->extend('TableSort')
->limit(50)
->orderByHeader($header)
->execute();
foreach ($result as $customer) {
$rows[] = array(
array(
'data' => $customer->ecid,
'align' => 'right',
),
array('data' => ec_customer_get_types('name', $customer->type)),
array('data' => ec_customer_get_name($customer)),
array('data' => theme('links', array('links' => module_invoke_all('link', 'ec_customer_list', $customer)))),
);
}
if (empty($rows)) {
$rows[] = array(
array(
'data' => t('No Customers found'),
'colspan' => count($header),
),
);
}
return theme('table', array('header' => $header, 'rows' => $rows));
}