* @copyright Copyright (c) 2010 United Prototype GmbH (http://unitedprototype.com) */ namespace UnitedPrototype\GoogleAnalytics\Internals; /** * This is nearly a 1:1 PHP port of the gaforflash X10 class code. * * @link http://code.google.com/p/gaforflash/source/browse/trunk/src/com/google/analytics/data/X10.as */ class X10 { /** * @var array */ protected $projectData = array(); /** * @var string */ protected $KEY = 'k'; /** * @var string */ protected $VALUE = 'v'; /** * @var array */ protected $SET = array('k', 'v'); /** * Opening delimiter for wrapping a set of values belonging to the same type. * @var string */ protected $DELIM_BEGIN = '('; /** * Closing delimiter for wrapping a set of values belonging to the same type. * @var string */ protected $DELIM_END = ')'; /** * Delimiter between two consecutive num/value pairs. * @var string */ protected $DELIM_SET = '*'; /** * Delimiter between a num and its corresponding value. * @var string */ protected $DELIM_NUM_VALUE = '!'; /** * Mapping of escapable characters to their escaped forms. * * @var array */ protected $ESCAPE_CHAR_MAP = array( "'" => "'0", ')' => "'1", '*' => "'2", '!' => "'3", ); /** * @var int */ protected $MINIMUM = 1; /** * @param int $projectId * @return bool */ protected function hasProject($projectId) { return isset($this->projectData[$projectId]); } /** * @param int $projectId * @param int $num * @param mixed $value */ public function setKey($projectId, $num, $value) { $this->setInternal($projectId, $this->KEY, $num, $value); } /** * @param int $projectId * @param int $num * @return mixed */ public function getKey($projectId, $num) { return $this->getInternal($projectId, $this->KEY, $num); } /** * @param int $projectId */ public function clearKey($projectId) { $this->clearInternal($projectId, $this->KEY); } /** * @param int $projectId * @param int $num * @param mixed $value */ public function setValue($projectId, $num, $value) { $this->setInternal($projectId, $this->VALUE, $num, $value); } /** * @param int $projectId * @param int $num * @return mixed */ public function getValue($projectId, $num) { return $this->getInternal($projectId, $this->VALUE, $num); } /** * @param int $projectId */ public function clearValue($projectId) { $this->clearInternal($projectId, $this->VALUE); } /** * Shared internal implementation for setting an X10 data type. * * @param int $projectId * @param string $type * @param int $num * @param mixed $value */ protected function setInternal($projectId, $type, $num, $value) { if(!isset($this->projectData[$projectId])) { $this->projectData[$projectId] = array(); } if(!isset($this->projectData[$projectId][$type])) { $this->projectData[$projectId][$type] = array(); } $this->projectData[$projectId][$type][$num] = $value; } /** * Shared internal implementation for getting an X10 data type. * * @param int $projectId * @param string $type * @param int $num * @return mixed */ protected function getInternal($projectId, $type, $num) { if(isset($this->projectData[$projectId][$type][$num])) { return $this->projectData[$projectId][$type][$num]; } else { return null; } } /** * Shared internal implementation for clearing all X10 data of a type from a * certain project. * * @param int $projectId * @param string $type */ protected function clearInternal($projectId, $type) { if(isset($this->projectData[$projectId]) && isset($this->projectData[$projectId][$type])) { unset($this->projectData[$projectId][$type]); } } /** * Escape X10 string values to remove ambiguity for special characters. * * @see X10::$escapeCharMap * @param string $value * @return string */ protected function escapeExtensibleValue($value) { $result = ''; $value = (string)$value; $length = strlen($value); for($i = 0; $i < $length; $i++) { $char = $value[$i]; if(isset($this->ESCAPE_CHAR_MAP[$char])) { $result .= $this->ESCAPE_CHAR_MAP[$char]; } else { $result .= $char; } } return $result; } /** * Given a data array for a certain type, render its string encoding. * * @param array $data * @return string */ protected function renderDataType(array $data) { $result = array(); $lastI = 0; ksort($data, SORT_NUMERIC); foreach($data as $i => $entry) { if(isset($entry)) { $str = ''; // Check if we need to append the number. If the last number was // outputted, or if this is the assumed minimum, then we don't. if($i != $this->MINIMUM && $i - 1 != $lastI) { $str .= $i; $str .= $this->DELIM_NUM_VALUE; } $str .= $this->escapeExtensibleValue($entry); $result[] = $str; } $lastI = $i; } return $this->DELIM_BEGIN . implode($this->DELIM_SET, $result) . $this->DELIM_END; } /** * Given a project array, render its string encoding. * * @param array $project * @return string */ protected function renderProject(array $project) { $result = ''; // Do we need to output the type string? As an optimization we do not // output the type string if it's the first type, or if the previous // type was present. $needTypeQualifier = false; $length = count($this->SET); for($i = 0; $i < $length; $i++) { if(isset($project[$this->SET[$i]])) { $data = $project[$this->SET[$i]]; if($needTypeQualifier) { $result .= $this->SET[$i]; } $result .= $this->renderDataType($data); $needTypeQualifier = false; } else { $needTypeQualifier = true; } } return $result; } /** * Generates the URL parameter string for the current internal extensible data state. * * @return string */ public function renderUrlString() { $result = ''; foreach($this->projectData as $projectId => $project) { $result .= $projectId . $this->renderProject($project); } return $result; } } ?>