'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)); }