--- a/busui/owa/modules/base/classes/client.php +++ b/busui/owa/modules/base/classes/client.php @@ -1,1 +1,611 @@ - + + * @copyright Copyright © 2006 Peter Adams + * @license http://www.gnu.org/copyleft/gpl.html GPL v2.0 + * @category owa + * @package owa + * @version $Revision$ + * @since owa 1.4.0 + */ + +class owa_client extends owa_caller { + + var $commerce_event; + + var $pageview_event; + + var $global_event_properties = array(); + + var $stateInit; + + // set one traffic has been attributed. + var $isTrafficAttributed; + + public function __construct($config = null) { + + $this->pageview_event = $this->makeEvent(); + $this->pageview_event->setEventType('base.page_request'); + + return parent::__construct($config); + } + + public function setPageTitle($value) { + $this->pageview_event->set('page_title', $value); + } + + public function setPageType($value) { + $this->pageview_event->set('page_type', $value); + } + + public function setProperty($name, $value) { + $this->setGlobalEventProperty($name, $value); + } + + private function setGlobalEventProperty($name, $value) { + + $this->global_event_properties[$name] = $value; + } + + private function getGlobalEventProperty($name) { + + if ( array_key_exists($name, $this->global_event_properties) ) { + return $this->global_event_properties[$name]; + } + } + + private function manageState( &$event ) { + + if ( ! $this->stateInit ) { + $this->setVisitorId( $event ); + $this->setFirstSessionTimestamp( $event ); + $this->setLastRequestTime( $event ); + $this->setSessionId( $event ); + $this->setNumberPriorSessions( $event ); + $this->setTrafficAttribution( $event ); + // clear old style session cookie + $session_store_name = sprintf('%s_%s', owa_coreAPI::getSetting('base', 'site_session_param'), $this->site_id); + owa_coreAPI::clearState( $session_store_name ); + + $this->stateInit = true; + } + } + + private function setVisitorId( &$event ) { + + $visitor_id = owa_coreAPI::getStateParam( 'v', 'vid' ); + + if ( ! $visitor_id ) { + $visitor_id = owa_coreAPI::getStateParam( 'v' ); + owa_coreAPI::clearState( 'v' ); + owa_coreAPI::setState( 'v', 'vid', $visitor_id, 'cookie', true ); + + } + + if ( ! $visitor_id ) { + $visitor_id = $event->getSiteSpecificGuid( $this->site_id ); + $this->setGlobalEventProperty( 'is_new_visitor', true ); + owa_coreAPI::setState( 'v', 'vid', $visitor_id, 'cookie', true ); + } + // set property on event object + $this->setGlobalEventProperty( 'visitor_id', $visitor_id ); + } + + private function setNumberPriorSessions( &$event ) { + // if check for nps value in vistor cookie. + $nps = owa_coreAPI::getStateParam('v', 'nps'); + // set value to 0 if not found. + if (!$nps) { + $nps = 0; + } + + // if new session, increment visit count and persist to state store + if ( $this->getGlobalEventProperty('is_new_session' ) ) { + owa_coreAPI::setState('v', 'nps', $nps + 1, 'cookie', true); + } + + // set property on the event object + $this->setGlobalEventProperty('num_prior_sessions', $nps); + } + + private function setFirstSessionTimestamp( &$event ) { + + $fsts = owa_coreAPI::getStateParam( 'v', 'fsts' ); + + if ( ! $fsts ) { + $fsts = $event->get('timestamp'); + owa_coreAPI::setState(owa_coreAPI::getSetting('base', 'visitor_param'), 'fsts', $fsts , 'cookie', true); + } + + $this->setGlobalEventProperty( 'fsts', $fsts ); + } + + private function setSessionId( &$event ) { + + $is_new_session = $this->isNewSession( $event->get( 'timestamp' ), $this->getGlobalEventProperty( 'last_req' ) ); + if ( $is_new_session ) { + //set prior_session_id + $prior_session_id = owa_coreAPI::getStateParam('s', 'sid'); + if ( ! $prior_session_id ) { + $state_store_name = sprintf('%s_%s', owa_coreAPI::getSetting('base', 'site_session_param'), $this->site_id); + $prior_session_id = owa_coreAPI::getStateParam($state_store_name, 's'); + } + if ($prior_session_id) { + $this->setGlobalEventProperty( 'prior_session_id', $prior_session_id ); + } + $session_id = $event->getSiteSpecificGuid( $this->site_id ); + // it's a new session. generate new session ID + $this->setGlobalEventProperty( 'session_id', $session_id ); + //mark new session flag on current request + $this->setGlobalEventProperty( 'is_new_session', true ); + owa_coreAPI::setState( 's', 'sid', $session_id, 'cookie', true ); + } else { + // Must be an active session so just pull the session id from the state store + $session_id = owa_coreAPI::getStateParam('s', 'sid'); + + // support for old style cookie + if ( ! $session_id ) { + $state_store_name = sprintf('%s_%s', owa_coreAPI::getSetting('base', 'site_session_param'), $this->site_id); + $session_id = owa_coreAPI::getStateParam($state_store_name, 's'); + owa_coreAPI::setState( 's', 'sid', $session_id, 'cookie', true ); + } + + $this->setGlobalEventProperty('session_id', $session_id); + } + + // fail-safe just in case there is no session_id + if ( ! $this->getGlobalEventProperty( 'session_id' ) ) { + $session_id = $event->getSiteSpecificGuid( $this->site_id ); + $this->setGlobalEventProperty( 'session_id', $session_id ); + //mark new session flag on current request + $this->setGlobalEventProperty( 'is_new_session', true ); + owa_coreAPI::debug('hello from failsafe'); + owa_coreAPI::setState( 's', 'sid', $session_id, 'cookie', true ); + } + } + + private function setLastRequestTime( &$event ) { + + $last_req = owa_coreAPI::getStateParam('s', 'last_req'); + + // suppport for old style cookie + if ( ! $last_req ) { + $state_store_name = sprintf( '%s_%s', owa_coreAPI::getSetting( 'base', 'site_session_param' ), $this->site_id ); + $last_req = owa_coreAPI::getStateParam( $state_store_name, 'last_req' ); + } + // set property on event object + $this->setGlobalEventProperty( 'last_req', $last_req ); + // store new state value + owa_coreAPI::setState( 's', 'last_req', $event->get( 'timestamp' ), 'cookie', true ); + } + + /** + * Check to see if request is a new or active session + * + * @return boolean + */ + private function isNewSession($timestamp = '', $last_req = 0) { + + $is_new_session = false; + + if ( ! $timestamp ) { + $timestamp = time(); + } + + $time_since_lastreq = $timestamp - $last_req; + $len = owa_coreAPI::getSetting( 'base', 'session_length' ); + if ( $time_since_lastreq < $len ) { + owa_coreAPI::debug("This request is part of a active session."); + return false; + } else { + //NEW SESSION. prev session expired, because no requests since some time. + owa_coreAPI::debug("This request is the start of a new session. Prior session expired."); + return true; + } + } + + /** + * Logs tracking event from url params taken from request scope. + * Takes event type from url. + * + * @return unknown + */ + function logEventFromUrl($manage_state = false) { + + // keeps php executing even if the client closes the connection + ignore_user_abort(true); + $service = &owa_coreAPI::serviceSingleton(); + $service->request->decodeRequestParams(); + $event = owa_coreAPI::supportClassFactory('base', 'event'); + $event->setEventType(owa_coreAPI::getRequestParam('event_type')); + $event->setProperties($service->request->getAllOwaParams()); + + // check for third party cookie mode. + $mode = owa_coreAPI::getRequestParam('thirdParty'); + if ( $mode ) { + return $this->trackEvent($event); + } else { + return owa_coreAPI::logEvent($event->getEventType(), $event); + } + } + + /** + * Logs tracking event + * + * This function fires a tracking event that will be processed and then dispatched + * + * @param object $event + * @return boolean + */ + public function trackEvent($event) { + + // do not track anything if user is in overlay mode + if (owa_coreAPI::getStateParam('overlay')) { + return false; + } + + // needed by helper page tags function so it can append to first hit tag url + if (!$this->getSiteId()) { + $this->setSiteId($event->get('site_id')); + } + + if (!$this->getSiteId()) { + $this->setSiteId(owa_coreAPI::getRequestParam('site_id')); + } + + // set various state properties. + $this->manageState( $event ); + + + $event = $this->setAllGlobalEventProperties( $event ); + + // send event to log API for processing. + return owa_coreAPI::logEvent($event->getEventType(), $event); + } + + public function setAllGlobalEventProperties( $event ) { + + if ( ! $event->get('site_id') ) { + $event->set( 'site_id', $this->getSiteId() ); + } + + // merge global event properties + foreach ($this->global_event_properties as $k => $v) { + $event->set($k, $v); + } + + return $event; + + } + + public function getAllEventProperties( $event ) { + + $event = $this->setAllGlobalEventProperties( $event ); + return $event->getProperties(); + } + + public function trackPageview($event = '') { + + if ($event) { + $event->setEventType('base.page_request'); + $this->pageview_event = $event; + } + return $this->trackEvent($this->pageview_event); + } + + public function trackAction($action_group = '', $action_name, $action_label = '', $numeric_value = 0) { + + $event = $this->makeEvent(); + $event->setEventType('track.action'); + $event->set('action_group', $action_group); + $event->set('action_name', $action_name); + $event->set('action_label', $action_label); + $event->set('numeric_value', $numeric_value); + $event->set('site_id', $this->getSiteId()); + return $this->trackEvent($event); + } + + /** + * Creates a ecommerce Transaction event + * + * Creates a parent commerce.transaction event + */ + public function addTransaction( + $order_id, + $order_source = '', + $total = 0, + $tax = 0, + $shipping = 0, + $gateway = '', + $country = '', + $state = '', + $city = '', + $page_url = '', + $session_id = '' + ) { + + $this->commerce_event = $this->makeEvent(); + $this->commerce_event->setEventType( 'ecommerce.transaction' ); + $this->commerce_event->set( 'ct_order_id', $order_id ); + $this->commerce_event->set( 'ct_order_source', $order_source ); + $this->commerce_event->set( 'ct_total', $total ); + $this->commerce_event->set( 'ct_tax', $tax ); + $this->commerce_event->set( 'ct_shipping', $shipping ); + $this->commerce_event->set( 'ct_gateway', $gateway ); + $this->commerce_event->set( 'page_url', $page_url ); + $this->commerce_event->set( 'ct_line_items', array() ); + $this->commerce_event->set( 'country', $page_url ); + $this->commerce_event->set( 'state', $page_url ); + $this->commerce_event->set( 'city', $page_url ); + if ( $session_id ) { + $this->commerce_event->set( 'original_session_id', $session_id ); + // tells the client to NOT manage state properties as we are + // going to look them up from the session later. + $this->commerce_event->set( 'is_state_set', true ); + } + } + + /** + * Adds a line item to a commerce transaction + * + * Creates and a commerce.line_item event and adds it to the parent transaction event + */ + public function addTransactionLineItem($order_id, $sku = '', $product_name = '', $category = '', $unit_price = 0, $quantity = 0) { + + if ( empty( $this->commerce_event ) ) { + $this->addTransaction('none set'); + } + + $li = array(); + $li['li_order_id'] = $order_id ; + $li['li_sku'] = $sku ; + $li['li_product_name'] = $product_name ; + $li['li_category'] = $category ; + $li['li_unit_price'] = $unit_price ; + $li['li_quantity'] = $quantity ; + + $items = $this->commerce_event->get( 'ct_line_items' ); + $items[] = $li; + $this->commerce_event->set( 'ct_line_items', $items ); + } + + /** + * tracks a commerce events + * + * Tracks a parent transaction event by sending it to the event queue + */ + public function trackTransaction() { + + if ( ! empty( $this->commerce_event ) ) { + $this->trackEvent( $this->commerce_event ); + $this->commerce_event = ''; + } + } + + public function createSiteId($value) { + + return md5($value); + } + + function getCampaignProperties( $event ) { + + $campaign_params = owa_coreAPI::getSetting( 'base', 'campaign_params' ); + $campaign_properties = array(); + $campaign_state = array(); + foreach ($campaign_params as $k => $param) { + //look for property on the event + $property = $event->get($param); + + // look for property on the request scope. + if ( ! $property ) { + $property = owa_coreAPI::getRequestParam($param); + } + if ( $property ) { + $campaign_properties[$k] = $property; + } + } + + // backfill values for incomplete param combos + + if (array_key_exists('at', $campaign_properties) && !array_key_exists('ad', $campaign_properties)) { + $campaign_properties['ad'] = '(not set)'; + } + + if (array_key_exists('ad', $campaign_properties) && !array_key_exists('at', $campaign_properties)) { + $campaign_properties['at'] = '(not set)'; + } + + if (!empty($campaign_properties)) { + //$campaign_properties['ts'] = $event->get('timestamp'); + } + + owa_coreAPI::debug('campaign properties: '. print_r($campaign_properties, true)); + + return $campaign_properties; + } + + function directAttributionModel( &$campaign_properties ) { + + // add new campaign info to existing campaign cookie. + if ( !empty( $campaign_properties ) ) { + $campaign_state = $this->getCampaignState(); + // add timestamp + //$campaign_properties['ts'] = $event->get('timestamp'); + // add new campaign into state array + $campaign_state[] = (object) $campaign_properties; + + // if more than x slice the first one off to make room + $count = count( $campaign_state ); + $max = owa_coreAPI::getSetting( 'base', 'max_prior_campaigns'); + if ($count > $max ) { + array_shift( $campaign_state ); + } + + // reset state + $this->setCampaignCookie($campaign_state); + + // set flag + $this->isTrafficAttributed = true; + } + + } + + function originalAttributionModel( &$campaign_properties ) { + + $campaign_state = $this->getCampaignState(); + // orignal touch was set previously. jus use that. + if (!empty($campaign_state)) { + // do nothing + // set the attributes from the first campaign touch + $campaign_properties = $campaign_state[0]; + $this->isTrafficAttributed = true; + + // no orginal touch, set one if it's a new campaign touch + } else { + + if (!empty($campaign_properties)) { + // add timestamp + //$campaign_properties['ts'] = $event->get('timestamp'); + owa_coreAPI::debug('Setting original Campaign attrbution.'); + $campaign_state[] = $campaign_properties; + // set cookie + $this->setCampaignCookie($campaign_state); + $this->isTrafficAttributed = true; + } + } + } + + function getCampaignState() { + + $campaign_state = owa_coreAPI::getStateParam( 'c' ); + if ( $campaign_state ) { + $campaign_state = json_decode( $campaign_state ); + } else { + $campaign_state = array(); + } + + return $campaign_state; + } + + function setTrafficAttribution( &$event ) { + + // if not then look for individual campaign params on the request. + // this happens when the client is php and the params are on the url + $campaign_properties = $this->getCampaignProperties( $event ); + if ( $campaign_properties ) { + $campaign_properties['ts'] = $event->get('timestamp'); + } + + // choose attribution model. + $model = owa_coreAPI::getSetting('base', 'trafficAttributionMode'); + switch ( $model ) { + + case 'direct': + owa_coreAPI::debug( 'Applying "Direct" Traffic Attribution Model' ); + $this->directAttributionModel( $campaign_properties ); + break; + case 'original': + owa_coreAPI::debug( 'Applying "Original" Traffic Attribution Model' ); + $this->originalAttributionModel( $campaign_properties ); + break; + default: + owa_coreAPI::debug( 'Applying Default (Direct) Traffic Attribution Model' ); + $this->directAttributionModel( $campaign_properties ); + } + + // if one of the attribution methods attributes the traffic them + // set attribution properties on the event object + if ( $this->isTrafficAttributed ) { + + owa_coreAPI::debug( 'Attributing Traffic to: %s', print_r($campaign_pproperties, true ) ); + + $this->applyCampaignPropertiesToEvent( $event, $campaign_properties ); + + // set campaign touches + $campaign_state = owa_coreAPI::getStateParam('c'); + if ($campaign_state) { + $this->setGlobalEventProperty( 'attribs', json_encode( $campaign_state ) ); + } + + } else { + owa_coreAPI::debug( 'No traffic attribution.' ); + } + } + + function applyCampaignPropertiesToEvent( $event, $campaign_properties) { + + // set the attributes + if (!empty($campaign_properties)) { + + foreach ($campaign_properties as $k => $v) { + + if ($k === 'md') { + $this->setGlobalEventProperty( 'medium', $campaign_properties[$k] ); + } + + if ($k === 'sr') { + $this->setGlobalEventProperty( 'source', $campaign_properties[$k] ); + } + + if ($k === 'cn') { + $this->setGlobalEventProperty( 'campaign', $campaign_properties[$k] ); + } + + if ($k === 'at') { + $this->setGlobalEventProperty( 'ad_type', $campaign_properties[$k] ); + } + + if ($k === 'ad') { + $this->setGlobalEventProperty( 'ad', $campaign_properties[$k] ); + } + + if ($k === 'tr') { + $this->setGlobalEventProperty( 'search_terms', $campaign_properties[$k] ); + } + } + } + } + + function setCampaignCookie($values) { + // reset state + owa_coreAPI::setState('c', '', + json_encode( $values ), + 'cookie', + owa_coreAPI::getSetting( 'base', 'campaign_attribution_window' ) ); + } + + // sets cookies domain + function setCookieDomain($domain) { + + if (!empty($domain)) { + $c = &owa_coreAPI::configSingleton(); + // sanitizes the domain + $c->setCookieDomain($domain); + } + } +} + +?>