<?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 googlyzer_homeWidget PHP class file.
*/

if (!class_exists('googlyzer_homeWidget')) {
	
	// googlyzer_homeWidget class
	class googlyzer_homeWidget {
		// set widget id and form id
		public $widgetId = 'dashboard_googlyzer'; // this must be the same as the widget id used in wp_add_dashboard_widget
		public $formId = 'dashboard_googlyzer_control'; // this is new, refers to control form
		public $optionId = 'dashboard_widget_options'; // array for all dashboard widgets to store options
		
		/*
		 * private variables
		 */
		private $parentInstance = 		null;
		
		// public constructor
		function googlyzer_homeWidget( $parentInstance ) {
			$this->__construct( $parentInstance );
		}
		
		// hidden constructor
		function __construct( $parentInstance ) {
			// record parent object reference
			$this->parentInstance = $parentInstance;
			
			if ( current_user_can( 'administrator' ) ) {
				// hook into wp dashboard setup action
				add_action(
					$tag = 'wp_dashboard_setup', 
					$callback = array( &$this, 'add_dash_widget' ),
					$priority = 100
					);
			}			
		}
			
		// add dashboard widget
		function add_dash_widget() {
			// add dashboard widget
			wp_add_dashboard_widget(
				$widgetId = 'dashboard_googlyzer',
				$widget_name = 'Googlyzer',
				$callback = array( &$this, 'dash_widget' ),
				$control_callback = array( &$this, 'dash_widget_control' )
				);
		} // end method add_dash_widget
		
		// method to output dashboard widget
		function dash_widget() {
			$html = false;
			// output html
			try {
				$html = googlyzer_Utilities::getFilteredHtml( GOOGLYZER_HOME_WIDGET_HTML );
			} catch ( googlyzer_Exception $e ) {
				echo $e->getError();
			}
			if ( $html ) {
			    // check to make sure that Google authorization has been completed and other options are set
			    if ( googlyzer_Utilities::validateSetup() ) {
			    	// retrieve general option group from parent
			    	$genOpts = googlyzer_Utilities::getOptions( 'googlyzer-options', 'standard' );
			    	$metrics = $this->getSelectedMetrics( 'associative' );
			    	$metricList = $this->getSelectedMetrics( 'list' );
			    	// determine first date of data with GA
			    	$dataStart = googlyzer_Utilities::determineGADataStart( $genOpts );
			    	// first populate today and total columns in key metric table
			    	$types = array( 'today', 'total' );
			    	foreach ( $types as $key => $type ) {
			    		$retry = true;
			    		$retry_count = 0;
			    		$max_retries = 1;
			    		while ( $retry && $retry_count <= $max_retries ) {
							$args = $this->setFeedArgs( array( 
								'dataStart' =>		$dataStart,
								'genOpts' => 		$genOpts, 
								'metric' => 		$metricList,
								'dimension' =>		array( 'ga:date' ),
								'sort' =>			'ascending',
								'sort_column' =>	'ga:date',
								'type' => 			$type,
								) );
							// create data feed object
							try {
								$feedObj = new googlyzer_DataFeed(
									$feedType =				'data_request',
									$url = 					'https://www.google.com/analytics/feeds/data?',
									$method = 				'GET',
									$feedSpecificSettings = array(
										'GA_AUTH_TOKEN' =>	$genOpts['ga_auth_token'],
										'feedArgs' =>		$args
										)
									);
								// request data from Google
								$feedObj->getFeed();
								// load XML data into data object
								$dataObj = $feedObj->handleFeed();
								$retry = false;
							} catch ( googlyzer_Exception $e ) {
								if ( !$genOpts['ga_auth_token'] = googlyzer_Utilities::checkException( $e, $genOpts ) ) {
									$retry = false;
								}
							}
							$retry_count++;
						}
						$html = $this->insertTableData( $dataObj, $html, $metrics, $type );
						$html = $this->insertTableRowLabels( $html, $metrics );
			    	}
			    	$retry = true;
			    	$retry_count = 0;
			    	$max_retries = 1;
			    	while ( $retry && $retry_count <= $max_retries ) {
				    	// then draw sparklines to show trend data
				    	// first, set up new feed object
				    	$args = $this->setFeedArgs( array(
				    		'dataStart' =>		$dataStart,
	    					'genOpts' => 		$genOpts, 
	    					'metric' => 		$metricList,
	    					'dimension' =>		array( 'ga:date' ),
	    					'sort' =>			'ascending',
	    					'sort_column' =>	'ga:date',
	    					'start' =>			30
				    		) );
				    	$feedObj = new googlyzer_DataFeed(
							$feedType =				'data_request',
							$url = 					'https://www.google.com/analytics/feeds/data?',
							$method = 				'GET',
							$feedSpecificSettings = array(
								'GA_AUTH_TOKEN' =>	$genOpts['ga_auth_token'],
								'feedArgs' =>		$args
								)
							);
				    	try {
					    	// then request data set from Google
					    	$feedObj->getFeed();
					    	// load XML data into data object
					    	$dataObj = $feedObj->handleFeed();
					    	$retry = false;
				    	} catch ( googlyzer_Exception $e ) {
				    		if ( !$genOpts['ga_auth_token'] = googlyzer_Utilities::checkException( $e, $genOpts ) ) {
								$retry = false;
							}
				    	}
				    	$retry_count++;
			    	}
					$html = $this->drawSparklines( $dataObj, $html, $metrics );
					// then add popular page stats
					$popularPage = array( 1 => 'day', 8 => 'week', 31 => 'month', 365 => 'year', 1000 => 'total' );
					// first, set up new feed object
					$args = $this->setFeedArgs( array(
						'dataStart' =>		$dataStart,
    					'genOpts' => 		$genOpts, 
						'max_results' =>	1,
    					'metric' => 		array( 'ga:pageviews' ),
    					'dimension' =>		array( 'ga:pageTitle' ),
    					'sort' =>			'descending',
    					'sort_column' =>	'ga:pageviews'
						) );
					foreach ( $popularPage as $key => $interval ) {
						$retry = true;
						$retry_count = 0;
						$max_retries = 1;
						while ( $retry && $retry_count <= $max_retries ) {
							$args = $this->setDates( $args, $dataStart, $key );
							$feedObj = new googlyzer_DataFeed(
								$feedType =				'data_request',
								$url = 					'https://www.google.com/analytics/feeds/data?',
								$method = 				'GET',
								$feedSpecificSettings = array(
									'GA_AUTH_TOKEN' =>	$genOpts['ga_auth_token'],
									'feedArgs' =>		$args
									)
								);
							try {
								// then request data set from Google
								$feedObj->getFeed();
								// load XML data into data object
								$dataObj = $feedObj->handleFeed();
								$retry = false;
							} catch ( googlyzer_Exception $e ) {
								if ( !$genOpts['ga_auth_token'] = googlyzer_Utilities::checkException( $e, $genOpts ) ) {
									$retry = false;
								}
							}
							$retry_count++;
						}
						$html = $this->insertPopularPageData( $dataObj, $args['end'], $interval, $html, $args['start'] );
					}
			    	echo $html;
			    	$this->add_dash_link( 'googlyzer-dashboard' );
		    	// otherwise Google authorization has not been completed
		    	}
    		} else {
    			// failed to load dashboard html
    		}
		} // end method dash_widget
		
		// method to add link to googlyzer dashboard on widget
		function add_dash_link( $target ) {
			// build html for link
			$html = '<table class="footer"><tbody><tr><td><div class="version">Googlyzer v' . 
				GOOGLYZER_VERSION . '</div></td><td><div><div id="link" class="link">' .
				'<a href="?page=googlyzer-dashboard"' .
				'>Access Googlyzer Dashboard</a>' .
				'</div></td></tr></tbody></table>'
				;
			echo $html;
		}
		
		// method for dashboard widget control
		function dash_widget_control() {
			// set up array for metrics selections
			$metrics = array( 
				'metric1' => 'ga:visits', 
				'metric2' => 'ga:newVisits', 
				'metric3' => 'ga:pageviews', 
				'metric4' => 'ga:visitors', 
				'metric5' => 'ga:avgTimeOnSite'
				);
			
			// check whether there are already dashboard widget options in the database
			if ( !$widget_options = get_option( $this->optionId ) ) {
				$widget_options = array();
			}
			
			// check whether we have information for this form
			if ( !isset( $widget_options[$this->widgetId] ) ) {
				$widget_options[$this->widgetId] = array();
			}
			
			// check whether our form was just submitted
			if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset($_POST[$this->formId] ) ) {
				// grab value sent for each metric
				foreach ( $metrics as $metric => $value ) {
					$widget_options[$this->widgetId][$metric] = $_POST[$this->formId][$metric];
				}
				// update widget options array
				update_option( $this->optionId, $widget_options );
			}
			
			// check if widget option was set previously, if not leave as default
			foreach ( $metrics as $metric => $value ) {
				if ( isset( $widget_options[$this->widgetId][$metric] ) ) {
					$metrics[$metric] = $widget_options[$this->widgetId][$metric];
				}
			}
			
			// output form fields
			foreach ( $metrics as $metric_option => $val_selected ) {
				// output field label
				echo '<p><label for="googlzyer-dashboard-widget-' . 
					$metric_option . '">' . 
					__('Metric ' . substr( $metric_option, 6) . 
					':') . '</label>&nbsp;&nbsp;';
				// populate and output dropdown
				echo '<select id="googlyzer-dashboard-widget-' . $metric_option . '" name="'.$this->formId.'['.$metric_option.']">';
				if ( is_array( $this->parentInstance->METRIC_SELECTIONS ) ) {
					// process associative array
					foreach ( $this->parentInstance->METRIC_SELECTIONS as $key => $value ) {
						$selected = ( $val_selected == $key ) ? 'selected="selected"' : '';
						echo "<option value='$key' $selected>$value</option>";
					}
				}
				echo "</select></p>";
			}
			
		}
		
		/**
		 * Method to display metric element (required for sparkline creation) once html document is ready.
		 * 
		 * @param string $metric
		 */
		private function displayElement( $metric ) {
			$element = "'" . $metric . "'";
			// before drawing sparkline, must output javascript to make page element visible
			?>
				<script type="text/javascript">
					jQuery(document).ready(function() { 
						googlyzer_ShowHideElements( true, new Array( <?php echo $element; ?> ) );
						});
				</script>
			<?php
		}
		
		/**
		 * Method to insert javascript for sparkline creation into html output.
		 * 
		 * @param object $dataObj
		 * @param string $html
		 * @param array $metrics
		 * @return string $html
		 */
		private function drawSparklines( $dataObj, $html, $metrics ) {
			$script = "<script type='text/javascript'>\njQuery(document).ready(function() {\n";
			// set up associative array for sparkline colors
			$colors = array(
				'metric1' => 		'#FF0000', // red
				'metric2' =>		'#009900', // green
				'metric3' => 		'#0000FF', // blue
				'metric4' => 		'#FF6600', // orange
				'metric5' => 		'#FF00FF' //fuscia
				);
			foreach ( $metrics as $metricNumber => $metricName) {
				$this->displayElement( $metricNumber );
				// set up linechart object
				$lineChart = new googlyzer_ChartObj( array(
					'composite' => 		false,
					'elementId' => 		$metricNumber,
					'fillColor' => 		false,
					'height' => 		'14px',
					'lineColor' => 		$colors[$metricNumber],
					'maxSpotColor' =>	false,
					'minSpotColor' =>	false,
					'spotColor' =>		false,
					'type' => 			'line',
					'values' =>			$dataObj->getMetricValuesString( $metric = $metricName, $period = 'current', $historyFlag = false ),
					'width' => 			'120px'
					) );
				// set up sparkline tags
				$lineChart->buildTags();
				$script .= $lineChart->getSparklineScript() . "\n";
			}
			// then output sparkline script for current data
			$script .= "});\n</script>";
			// insert scripts into html for output
			$html = str_replace( '<!-- widget_script -->', $script, $html );
			return $html;
		}
		
		/**
		 * Method to get list of selected metrics.
		 * 
		 * @param string $returnType
		 * @return string $returnList
		 * @return array $returnList
		 */
		private function getSelectedMetrics( $returnType ) {
			$metricOptions = array( 'metric1', 'metric2', 'metric3', 'metric4', 'metric5' );
			$widgetOptions = $this->getWidgetOptions();
			$returnList = array();
			foreach ( $metricOptions as $key => $metricOption ) {
				if ( array_key_exists( $metricOption, $widgetOptions[$this->widgetId] ) && $widgetOptions[$this->widgetId][$metricOption] !== 'none' ) {
					if ( $returnType === 'list' ) {
						array_push( $returnList, $widgetOptions[$this->widgetId][$metricOption] );
					} else {
						$returnList[$metricOption] = $widgetOptions[$this->widgetId][$metricOption];
					}
				}
			}
			return $returnList;
		}
		
		/**
		 * Method to retrieve widget options (metric selections).
		 * 
		 * @return array $widgetOptions
		 */
		private function getWidgetOptions() {
			if ( !$widgetOptions = get_option( $this->optionId ) ) {
				$widgetOptions = $this->getDefaults();
			}
			return $widgetOptions;
		}
		
		/**
		 * Method to set default metric selections.
		 * 
		 * @return array $options
		 */
		private function getDefaults() {
			$options = array( 
				'metric1' => 'ga:visits', 
				'metric2' => 'ga:newVisits', 
				'metric3' => 'ga:pageviews', 
				'metric4' => 'ga:visitors', 
				'metric5' => 'ga:avgTimeOnSite'
				);
			return $options;
		}
		
		/**
		 * Method to set dates for Google Analytics data request.
		 * 
		 * @param array $args
		 * @param string $dataStart
		 * @param string $start
		 * @param string $type
		 * @return array $args
		 */
		private function setDates( $args, $dataStart, $start, $type = '' ) {
			// check for specific start date request
			if ( !empty( $start ) ) {
				$start = googlyzer_Utilities::newDateString( array(
					'change'=>		'decrease', 
					'amount'=>		$start, 
					'format'=>		'ga_request' 
					) );
				// create data objects for comparison
				$startObj = new DateTime( $start );
				$dataStartObj = new DateTime( $dataStart );
				// if start is prior to beginning of GA data, use date for first occurrence of GA data
				if ( $startObj < $dataStartObj ) {
					$args['start'] = $dataStart;
				// otherwise, use requested start date
				} else {
					$args['start'] = $start;
				}
			// if request is today, set start to current date
			} elseif ( $type === 'today' ) {
				$args['start'] = googlyzer_Utilities::defaultEndDate();
			// otherwise set to date of first occurrence of GA data
			} else {
				$args['start'] = $dataStart;
			}
			$args['end'] = googlyzer_Utilities::defaultEndDate();
			return $args;
		}
		
		/**
		 * Method to insert popular page data into html output.
		 * 
		 * @param object $dataObj
		 * @param string $endDate
		 * @param string $interval
		 * @param string $html
		 * @param string $startDate
		 * @return string $html
		 */
		private function insertPopularPageData( $dataObj, $endDate, $interval, $html, $startDate ) {
			$views = $dataObj->getMetricValue( 'ga:pageviews' );
			$title = googlyzer_Utilities::pageTitleFilter( $dataObj->getDimensionValue( 'ga:pageTitle' ) );
			$comments = googlyzer_Utilities::getCommentCountByTitle( $endDate, $title, $startDate );
			$title = googlyzer_Utilities::linkifyTitle( $title );
			$html = str_replace( '<!-- pageTitle_' . $interval . ' -->', $title, $html );
			$html = str_replace( '<!-- comments_' . $interval . ' -->', $comments, $html );
			$html = str_replace( '<!-- pageviews_' . $interval . ' -->', $views, $html );
			return $html;
		}
		
		/**
		 * Method to insert metric stats into table data html output.
		 * 
		 * @param object $dataObj
		 * @param string $html
		 * @param array $metrics
		 * @param string $period
		 * @return string $html
		 */
		private function insertTableData( $dataObj, $html, $metrics, $period ) {
			$dataObj->analyze( $type = 'line', $chartOpts = array() );
			foreach ( $metrics as $key => $metric ) {
				// catch metrics that are averages, rates, or percents and divide by number of values
				if ( preg_match( "/avg/i", $metric ) || preg_match( "/rate/i", $metric ) || preg_match( "/percent/i", $metric ) ) {
					$value = $dataObj->getMetaDataVal( 'avg', $metric, 'current' );
				} else {
					$value = $dataObj->getMetaDataVal( 'total', $metric, 'current' );
				}
				// convert to minutes for time specific metrics
				if ( preg_match( "/time/i", $metric ) ) {
					$value = $value / 60;
				}
				if ( strpos( $value, "." ) ) {
					$value = round( $value, 2 );
				}
				$html = str_replace( '<!-- ' . $key . '_' . $period . ' -->', $value, $html );
			}
			return $html;
		}
		
		/**
		 * Method to insert row labels based on selected metrics into html output.
		 * 
		 * @param string $html
		 * @param array $metrics
		 * @return string $html
		 */
		private function insertTableRowLabels( $html, $metrics ) {
			foreach ( $metrics as $key => $metric ) {
				$label = $this->parentInstance->getMetricLabel( $metric );
				if ( $metric === 'ga:avgTimeOnSite' ) {
					$label .= " (min)";
				}
				$html = str_replace( '<!-- ' . $key . '_label -->', $label, $html );
			}
			return $html;
		}
		
		/**
		 * Method to set arguments for Google data feed.
		 * 
		 * @param array $params
		 * @return array $args
		 */
		private function setFeedArgs( $params ) {
			extract( $params );
			$start = ( isset( $start ) ) ? $start : '';
			$type = ( isset( $type ) ) ? $type : '';
			$args = array();
			$args['id'] = $genOpts['ga_tableid'];
			$args['metrics'] = $metric;
			$args['dimensions'] = $dimension;
			$args['sort'] = $sort;
			$args['sort_column'] = $sort_column;
			$args['max_results'] = 10000;
			$args = $this->setDates( $args, $dataStart, $start, $type );
			return $args;			
		}
	
	} // end class googlyzer_homeWidget
	
} // end if googlyzer_homeWidget exists
?>