<?php

/*
	Googlyzer
	Contact: Bill Edgar (bill.edgar@oaktondata.com)
	http://www.oaktondata.com/googlyzer
	
	Copyright (c) 2011-2012, Oakton Data LLC
	All rights reserved.

	Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
	    
	    * Redistributions of source code must retain the above copyright
	      notice, this list of conditions and the following disclaimer.
	    * Redistributions in binary form must reproduce the above copyright
	      notice, this list of conditions and the following disclaimer in the
	      documentation and/or other materials provided with the distribution.
	    * Neither the name of the Oakton Data LLC nor the
	      names of its contributors may be used to endorse or promote products
	      derived from this software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
	ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
	DISCLAIMED. IN NO EVENT SHALL Oakton Data LLC BE LIABLE FOR ANY
	DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	
	This is the googlyzer_AnalyticsDataObj PHP class file.
	
 */

/* 
 * create aggregateObj helper class
 */
if ( !class_exists( 'googlyzer_aggregateObj' ) ) {
	class googlyzer_aggregateObj {
		public $name = 					null;
		public $type = 					null;
		public $value = 				null;
		
		public function googlyzer_aggregateObj( $args ) {
			$this->__construct( $args );
		}
		
		public function __construct( $args ) {
			extract( $args );
			$this->name = 				$name;
			$this->type = 				$type;
			$this->value = 				$value;
		}
	}
}

/*
* first create dataSourceObj helper class
*/
if ( !class_exists( 'googlyzer_dataSourceObj' ) ) {
	class googlyzer_dataSourceObj {
		public $tableId = 				null;
		public $tableName = 			null;
		public $profileId = 			null;
		public $webPropertyId = 		null;
		public $accountName = 			null;

		public function googlyzer_dataSourceObj( $args ) {
			$this->__construct( $args );
		}

		public function __construct( $args ) {
			extract( $args );
			$this->tableId = 			$tableId;
			$this->tableName = 			$tableName;
			$this->profileId = 			$profileId;
			$this->webPropertyId = 		$webPropertyId;
			$this->accountName = 		$accountName;
		}
	}
}


if ( !class_exists( 'googlyzer_AnalyticsDataObj' ) ) {

	/** googlyzer_AnalyticsDataObj class
	 *
	 * PHP object class to store and manipulate data from Google Analytics.
	 * @author wmsedgar
	 *
	 */
	class googlyzer_AnalyticsDataObj {
		/*
		 * public variables
		 */
		public $ENTRIES = 				array();
		public $METRICS = 				array();
		public $DIMENSIONS = 			array();
		
		/*
		 * private variables
		 */
		private $aggregates = 			array(); // to hold aggregateObj instances
		private $containsSampleData = 	false;
		private $dataSource = 			array(); // to hold dataSourceObj instances
		private $end = 					null;
		private $pieData = 				array();
		private $start = 				null;
		private $startIndex = 			1; // default is 1 not 0
		private $totalResults = 		0;

		// old style constructor method for backward PHP compatibility
		public function googlyzer_AnalyticsDataObj() {
			$this->__construct();
		}

		// public constructor method
		public function __construct() {
			
		}
		
		/*
		 * private methods
		 */
		
		/**
		 * 
		 * private method for capturing metadata for a specific metric
		 * 
		 * @param string $metric (ga:metric_name)
		 * @param string $set (current or history)
		 * @param boolean $history
		 */
		private function loadMetaData( $metric, $set = 'current', $historyFlag = true ) {
			// load metric values
			$metricVals = ( $historyFlag ) ? $this->METRICS[$metric][$set]['values'] : $this->getMetricValuesList( $metric );
			// record metadata values
			$metadata = $this->recordMetaData( $metricVals );
			// load metadata into metric properties
			foreach ( $metadata as $key => $value ) {
				$this->METRICS[$metric][$set][$key] = $value;
			}
		}
		
		/**
		 * 
		 * private method for capturing metadata for a list of values
		 * 
		 * @param array $myVals
		 * @return array $metadata
		 */
		private function recordMetaData( $myVals ) {
			// now calculate metadata values
			$metadata = array();			
			$metadata['max'] = max( $myVals );
			$metadata['min'] = min( $myVals );
			$metadata['count'] = count( $myVals );
			$metadata['last'] = end( $myVals );
			$metadata['previous'] = prev( $myVals );
			$metadata['total'] = array_sum( $myVals );
			$metadata['avg'] = $metadata['total'] / $metadata['count'];
			$metadata = googlyzer_Utilities::roundAssociativeArrayvals( $metadata, 1 );
			return $metadata;
		}
		
		/**
		 * 
		 * private method for capturing the avg value trend between 2 lists of values
		 * 
		 * @param string $metric
		 */
		private function recordMetricTrends( $metric ) {
			$currentAvg = $this->METRICS[$metric]['current']['avg'];
			$historyAvg = $this->METRICS[$metric]['history']['avg'];
			$this->METRICS[$metric]['dateRangeTrend'] = round( ( ( $currentAvg - $historyAvg ) / $historyAvg ) * 100, 1 );
			$this->METRICS[$metric]['dailyTrend'] = round( ( ( $this->METRICS[$metric]['current']['last'] - 
				$this->METRICS[$metric]['current']['previous'] ) / $this->METRICS[$metric]['current']['previous'] ) * 100, 1 );
		}
		
		/**
		 * 
		 * private method for capturing slice percentages for values to be used in a piechart
		 */
		private function recordPieData() {
			$pieData = array();
			if ( count( $this->DIMENSIONS ) == 1 ) {
				// get dimension array key
				$dimension = key( $this->DIMENSIONS );
				// get metric array key
				$metric = key( $this->METRICS );
				// total values for metric
				$pieTotal = array_sum( $this->getMetricValuesList( $metric ) );
				// loop for dimension and metric values
				foreach ( $this->ENTRIES as $entryNumber => $entryArray ) {
					// calculate and record % of total pie
					$pieData[$entryArray['dimensions'][$dimension]['value']] = 
						round( $entryArray['metrics'][$metric]['value'] / $pieTotal * 100, 0 );
				}
				$this->pieData = $pieData;
			}
		}
		
		/**
		 * 
		 * private method to split list of values in half -> current & history
		 * 
		 * @param string $metric
		 */
		private function splitMetricHistory( $metric ) {
			$metricValues = $this->getMetricValuesList( $metric );
			$midPoint = round( count( $metricValues ) / 2, 0 );
			$history = array_splice( $metricValues, 0, $midPoint );
			$this->METRICS[$metric]['history']['values'] = $history;
			$this->METRICS[$metric]['current']['values'] = $metricValues;
		}
		
		/*
		* public methods
		*/
		
		/**
		 * 
		 * public method for recording an aggregate value
		 * 
		 * @param array $args
		 */
		public function addAggregate( $args ) {
			extract( $args );
			$a = new googlyzer_aggregateObj( array(
				'name' =>			$name, 
				'type' =>			$type, 
				'value' =>			$value
				) );
			array_push( $this->aggregates, $a );
		}
		
		/**
		 * 
		 * public method for recording a data source and it's info
		 * 
		 * @param array $args
		 */
		public function addDataSource( $args ) {
			extract( $args );
			$d = new googlyzer_dataSourceObj( array(
				'tableId' =>		$tableId,
				'tableName' =>		$tableName,
				'profileId' =>		$profileId,
				'webPropertyId' =>	$webPropertyId,
				'accountName' =>	$accountName
				) );
			array_push( $this->dataSource, $d );
		}
		
		/**
		 * 
		 * public method for adding a dimension to data object entry
		 * 
		 * @param string $dimension
		 * @param integer $entryNumber
		 */
		public function addDimension( $dimension, $entryNumber ) {
			// check for existence of dimension key
			if ( !array_key_exists( $dimension, $this->ENTRIES[$entryNumber]['dimensions'] ) ) {
				// add to list of dimensions if not there
				$this->ENTRIES[$entryNumber]['dimensions'][$dimension] = array();
				$this->ENTRIES[$entryNumber]['dimensions'][$dimension]['value'] = null;
				$this->DIMENSIONS[$dimension] = array();
				$this->DIMENSIONS[$dimension]['current']['values'] = array();
			}
		}
		
		/**
		 * 
		 * public method to add data object entry
		 * 
		 * @param integer $entryNumber
		 */
		public function addEntry( $entryNumber ) {
			if ( !array_key_exists( $entryNumber, $this->ENTRIES ) ) {
				$this->ENTRIES[$entryNumber] = array();
				$this->ENTRIES[$entryNumber]['dimensions'] = array();
				$this->ENTRIES[$entryNumber]['metrics'] = array();
			}
		}
		
		/**
		 * 
		 * public method for adding a metric to data object entry
		 * 
		 * @param string $metric
		 * @param string $type
		 * @param integer $entryNumber
		 */
		public function addMetric( $metric, $type, $entryNumber ) {
			// check for existence of dimension key
			if ( !array_key_exists( $metric, $this->ENTRIES[$entryNumber]['metrics'] ) ) {
				// add to list of dimensions if not there
				$this->ENTRIES[$entryNumber]['metrics'][$metric] = array();
				$this->ENTRIES[$entryNumber]['metrics'][$metric]['value'] = null;
				$this->ENTRIES[$entryNumber]['metrics'][$metric]['type'] = $type;
				$this->METRICS[$metric] = array();
				$this->METRICS[$metric]['current']['values'] = array();
				$this->METRICS[$metric]['history']['values'] = array();
			}
		}
		
		/**
		 * 
		 * public method to analyze a data set and develop values to be used in display of chart
		 * 
		 * @param string $type
		 * @param array $chartOpts
		 */
		public function analyze( $type, $chartOpts ) {
			if ( $type === 'line' || $type === 'composite' ) {
				$history = false; // assume no history
				foreach ( $this->METRICS as $metricName => $metricProperties ) {
					// first check to see if history is enabled
					if ( array_key_exists( 'history_enable', $chartOpts ) && $chartOpts['history_enable'] === 'enabled' ) {
						// if history is enabled, separate values for history and current arrays
						$this->splitMetricHistory( $metricName );
						$history = true; // set history flag for later use
					}
					// then load base metadata
					$this->loadMetaData( $metric = $metricName, $set = 'current', $historyFlag = $history );
					// if history is enabled, get metadata for history values and date range trend
					if ( $history ) { 
						$this->loadMetaData( $metric = $metricName, $set = 'history', $historyFlag = $history ); 
						$this->recordMetricTrends( $metricName );
					}
				}
			// if dealing with a piechart, get slice percentages
			} elseif ( $type === 'pie' ) {
				$this->recordPieData();
			}
		}
		
		public function entrySearch( $patterns, $retrieve, $name ) {
			$condition = array(); // array to track result of condition comparisons
			$result = array(); // used to pass result back to calling function
			foreach ( $this->ENTRIES as $entryNumber => $entryArray ) {
				foreach ( $patterns as $dimensionName => $searchOptions ) {
					if ( strpos( $entryArray[$searchOptions['type']][$dimensionName]['value'], $searchOptions['pattern'] ) !== false ) {
					// pattern was matched
						switch ( $searchOptions['operation'] ) {
							case 'equal':
								array_push( $condition, true );
								break;
							case 'not_equal':
								array_push( $condition, false );
								break;
						}
					// pattern was not matched
					} else {
						switch ( $searchOptions['operation'] ) {
							case 'equal':
								array_push( $condition, false );
								break;
							case 'not_equal':
								array_push( $condition, true );
								break;
						}
					}				
				}
				// if all conditions were met, record desired value
				if ( !in_array( false, $condition ) ) {
					if ( !array_key_exists( $entryArray[$retrieve][$name]['type'], $result ) ) {
						$result[$entryArray[$retrieve][$name]['type']] = array();
					}
					array_push( $result[$entryArray[$retrieve][$name]['type']], $entryArray[$retrieve][$name]['value'] );
				}
				// reset condition array for next round of checks
				$condition = array();
			}
			return $result;
		}
		
		/**
		*
		* public method for retrieving array of dimension values
		*
		* @param string $dimension (ga:dimension_name)
		* @return array $dimensionVals
		*/
		public function getDimensionValuesList( $dimension = '' ) {
			$dimensionVals = array();
			if ( empty( $dimension ) ) {
				$dimension = key( $this->DIMENSIONS );
			}
			foreach ( $this->ENTRIES as $entryNumber => $entryArray ) {
				array_push( $dimensionVals, $entryArray['dimensions'][$dimension]['value'] );
			}
			return $dimensionVals;
		}
		
		public function getDimensionValue( $dimension ) {
			$number = key( $this->ENTRIES );
			return $this->ENTRIES[$number]['dimensions'][$dimension]['value'];
		}
		
		/**
		*
		* public method for retrieving a comma-delimited list of dimension values
		*
		* @param string $dimension
		* @param string $period
		*/
		public function getDimensionValuesString( $dimension, $period = 'current', $historyFlag = true ) {
			if ( empty( $dimension ) ) {
				$metric = key( $this->DIMENSIONS );
			}
			if ( $historyFlag ) {
				return join( ",", $this->DIMENSIONS[$dimension][$period]['values'] );
			} else {
				return join( ",", $this->getDimensionValuesList( $dimension ) );
			}
		}
		
		/**
		 * 
		 * public method for retrieval of a specific metadata value (min, max, avg, last, etc.)
		 * 
		 * @param string $key (min, max, avg, last, etc.)
		 * @param string $metric (ga:metric_name)
		 * @param string $period (current or history)
		 * @return unknown_type $value
		 */
		public function getMetaDataVal( $key, $metric, $period ) {
			return $this->METRICS[$metric][$period][$key];
		}
		
		public function getMetrics() {
			$metrics = array();
			foreach ( $this->METRICS as $metricName => $metricProperties ) {
				array_push( $metrics, $metricName );
			}
			return join( ",", $metrics );
		}
		
		public function getMetricValue( $metric ) {
			reset( $this->ENTRIES );
			$number = key( $this->ENTRIES );
			if ( !isset( $number ) || !is_array( $this->ENTRIES[$number]['metrics'][$metric] ) ) {
				return 0;
			} else {
				return $this->ENTRIES[$number]['metrics'][$metric]['value'];
			}
		}
		
		/**
		 * 
		 * public method for retrieving array of metric values
		 * 
		 * @param string $metric (ga:metric_name)
		 * @return array $metricVals
		 */
		public function getMetricValuesList( $metric = '' ) {
			$metricVals = array();
			if ( empty( $metric ) ) {
				$metric = key( $this->METRICS );
			}
			foreach ( $this->ENTRIES as $entryNumber => $entryArray ) {
				array_push( $metricVals, $entryArray['metrics'][$metric]['value'] );
			}
			return $metricVals;
		}
		
		/**
		 * 
		 * public method for retrieving a comma-delimited list of metric values
		 * 
		 * @param string $metric
		 * @param string $period
		 */
		public function getMetricValuesString( $metric, $period = 'current', $historyFlag = true ) {
			if ( empty( $metric ) ) {
				$metric = key( $this->METRICS );
			}
			if ( $historyFlag ) {
				return join( ",", $this->METRICS[$metric][$period]['values'] );
			} else {
				return join( ",", $this->getMetricValuesList( $metric ) );
			}
		}
		
		/**
		 * 
		 * public method to get piechart data array for chart labels
		 * 
		 * @return array $this->pieData
		 */
		public function getPieData() {
			return $this->pieData;
		}
		
		/**
		 * 
		 * public method to retrieve dimension / metric data and return as sorted (descending) table rows by metric value
		 * 
		 * @return array $rows
		 */
		public function getTableRows() {
			$rows = array();
			foreach ( $this->ENTRIES as $entryNumber => $entryArray ) {
				$dimensionKey = key( $entryArray['dimensions'] );
				$dataKey = googlyzer_Utilities::pageTitleFilter( $entryArray['dimensions'][$dimensionKey]['value'] );
				$metricKey = key( $entryArray['metrics'] );
				$rows[$dataKey] = $entryArray['metrics'][$metricKey]['value'];
			}
			arsort( $rows, $sort_flags = SORT_NUMERIC );
			return $rows;
		}
		
		/**
		 * 
		 * public method to retrieve metric trend data
		 * 
		 * @param string $trend (dailyTrend or dateRangeTrend)
		 * @param string $metric (ga:metric_name)
		 * @return integer
		 */
		public function getTrend( $trend, $metric ) {
			return $this->METRICS[$metric][$trend];
		}
		
		/**
		 * 
		 * public method for pushing a dimension value into a list property of data object
		 * 
		 * @param string $dimension
		 * @param unknown_type $value
		 * @param integer $entryNumber
		 */
		public function pushDimensionVal( $dimension, $value, $entryNumber ) {
			$value = ( isset( $value ) ) ? $value : 0;
			$this->ENTRIES[$entryNumber]['dimensions'][$dimension]['value'] = $value;
		}
		
		/**
		*
		* public method for pushing a metric value into a list property of data object
		*
		* @param string $metric
		* @param unknown_type $value
		* @param integer $entryNumber
		*/
		public function pushMetricVal( $metric, $value, $entryNumber ) {
			$value = ( isset( $value ) ) ? $value : 0;
			$this->ENTRIES[$entryNumber]['metrics'][$metric]['value'] = $value;
		}
		
		/**
		 * 
		 * public method for setting metric value type
		 * 
		 * @param string $metric
		 * @param string $type
		 */
		public function setMetricType( $metric, $type ) {
			foreach ( $this->ENTRIES as $entryNumber => $entryProperties ) {
				$entryProperties['metrics'][$metric]['type'] = $type;
			}
		}
		
		/**
		 *
		 * public method setProperties to set object attributes using associative array.
		 *
		 * @param array $properties
		 */
		public function setProperties( $properties ) {
			foreach ( $properties as $key => $value ) {
				$this->$key = $value;
			}
		}
		
	} // end class googlyzer_AnalyticsDataObj
	
} // end if class exists

?>