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

if ( !class_exists( 'googlyzer_Utilities' ) ) {

	/** googlyzer_Utilities class
	 *
	 * static PHP class for general tools/utilities.
	 * @author wmsedgar
	 *
	 */
	abstract class googlyzer_Utilities {
		/* public variables */
		
		/* private variables */
		
		/**
		 * old style constructor method for backward PHP compatibility
		 */ 
		public function googlyzer_Utilities() {
			$this->__construct();
		}
		
		/**
		 * public constructor method
		 */
		public function __construct() {
			
		}
		
		/*
		 * private functions
		 */
		
		/*
		 * public functions
		 */
		
		/**
		 * 
		 * Find string $search and replace with string $replace within $buffer.
		 * 
		 * @param string $buffer
		 * 
		 * @return string
		 */
		public function bufferFilter( $buffer ) {
			return str_replace( 'h3', 'p', $buffer );
		}
		
		/**
		 * 
		 * Method to check Google Feed exception for invalid GA token, and retrieve new GA token if appropriate.
		 * 
		 * @param googlyzer_Exception $e
		 * @param array $genOpts
		 * @throws googlyzer_Exception
		 * @return boolean
		 */
		public function checkException( $e, $genOpts ) {
			// check for 401 or 403 error
			if ( preg_match( "/401/", $e->getError() ) || preg_match( "/403/", $e->getError() ) ) {
				// if google login credentials have been saved
				if ( !empty( $genOpts['ga_login'] ) && !empty( $genOpts['ga_password'] ) ) {
					// attempt to retrieve new google service authorization token
					$ga_auth_token = googlyzer_Utilities::getNewGoogleAuthToken( $genOpts );
					// if token is successfully retrieved
					if ( !empty( $ga_auth_token ) ) {
						// save it
						$genOpts['ga_auth_token'] = $ga_auth_token;
						// if successfully updated options array in WP database
						if ( update_option( $option_name = 'googlyzer-options', array( 'standard' => $genOpts ) ) ) {
							// return new GA token
							return $genOpts['ga_auth_token'];
						// otherwise, failed to update options array in WP database
						} else {
							// return false
							return false;
						}
					// if token was not successfully retrieved, throw exception						
					} else {
						throw new googlyzer_Exception( $e->getError() );
					}
					// google login credentials have not been saved
				} else {
					// throw error
					throw new googlyzer_Exception( $e->getError() );
				}
			} else {
				throw new googlyzer_Exception( $e->getError() );
			}
		}
		
		/**
		*
		* Method to validate format of GA metric or dimension.
		*
		* @param string $field
		*
		* @return boolean
		*/
		public function checkGAField( $field ) {
			if ( !preg_match( "/^ga:[a-zA-Z]{4,25}/", $field ) ) {
				return false;
			} else {
				return true;
			}
		}
		
		/**
		*
		* Method to convert dates between YYYY-MM-DD and MM-DD-YYYY formats.
		*
		* @param string $date (formatted as YYYY-MM-DD or MM-DD-YYYY)
		* @param string $format (ga_request (YYYY-MM-DD) or googlyzer (MM-DD-YYYY))
		* 
		* @return string $new_date
		*/
		public function convertDateString( $date, $format ) {
			switch ( $format ) {
				case 'ga_request':
					$date_array = explode( '-', $date );
					$new_date = $date_array[2] . '-' . $date_array[0] . '-' . $date_array[1];
					break;
				case 'googlyzer':
					$date_array = explode( '-', $date );
					$new_date = $date_array[1] . '-' . $date_array[2] . '-' . $date_array[0];
					break;
			}
			return $new_date;
		}
		
		/**
		 * 
		 * Enter description here ...
		 * @param string $date1 (date in mm-dd-YYYY format)
		 * @param string $date2 (date in mm-dd-YYYY format)
		 * @return integer difference in number of days
		 */
		public function dateDiff( $date1, $date2 ) {
			// Set timezone
			date_default_timezone_set( 'UTC' );
			$start = googlyzer_Utilities::convertDateString( $date1, 'ga_request' );
			$end = googlyzer_Utilities::convertDateString( $date2, 'ga_request' );
			$interval = round( ( (int) strtotime( $start ) - (int) strtotime( $end ) ) / ( 60 * 60 * 24 ) );
			return $interval;
		}
		
		/**
		*
		* Method to return default end date.
		*
		* @return string
		*/
		function defaultEndDate() {
			return date( 'Y-m-d' );
		}
		
		/**
		 *
		 * Method to return default start date.
		 *
		 * @return string
		 */
		function defaultStartDate() {
			return googlyzer_Utilities::newDateString( array(
				'amount' => 30,  
				'change' => 'decrease',
				'format' => 'ga_request'
				) );
		}
		
		/**
		 * 
		 * Method to retrieve date of first occurrence of data with GA for selected profile.
		 * 
		 * @param array $genOpts
		 * 
		 * @return string
		 */
		function determineGADataStart( $genOpts ) {
			$args = array(
	    		'dimensions' =>		array( 'ga:date' ),
	    		'end' =>			googlyzer_Utilities::defaultEndDate(),
	    		'filters' =>		'ga:visitors!=0', 
				'id' => 			$genOpts['ga_tableid'], 
				'max_results' =>	1,
				'metrics' => 		array( 'ga:visitors' ),
				'sort' =>			'ascending',
				'sort_column' =>	'ga:date',
				'start' =>			'2005-01-01'
				);
			$date = googlyzer_Utilities::defaultStartDate();
			$retry = true;
			$retry_count = 0;
			$max_retries = 1;
			while ( $retry && $retry_count <= $max_retries ) {
				// 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();
					$date = googlyzer_Utilities::convertGADateResponse( $dataObj->getDimensionValue( 'ga:date' ) );
					$retry = false;
				} catch ( googlyzer_Exception $e ) {
					try {
						if ( !$genOpts['ga_auth_token'] = googlyzer_Utilities::checkException( $e, $genOpts ) ) {
							$retry = false;
						}
					} catch ( googlyzer_Exception $e ) {
						throw new googlyzer_Exception( $e->getError() );
					}
				}
				$retry_count++;
			}
			return $date;
		}
		
		function convertGADateResponse( $dateString ) {
			$year = substr( $dateString, 0, 4 );
			$month = substr( $dateString, 4, 2 );
			$day = substr( $dateString, 6, 2 );
			return $year . "-" . $month . "-" . $day;
		}
		
		/**
		*
		* method to check for domain selection
		*
		* @return boolean
		*/
		public function domainSaved() {
			$options = get_option(
				$show = 'googlyzer-options',
				$default = false
				);
			if ( empty( $options['standard']['website'] ) ) {
				?>
    				<p>
    					Error: No domain has been selected.<br>
    				    Please return to the General tab on the Googlyzer Settings page, select a domain, and save your settings.
    				</p>
				<?php
				return false;
			} else {
				return true;
			}
		}
		
		/**
		*
		* Method to output sparkline script based on data and options input.
		*
		* @param array $args
		* @param object $dataObj
		* @param string $elementID
		* @param string $html
		* @param string $metric
		* 
		* @return string $html
		*/
		public function drawSparkline( $args, $dataObj, $html, $metric ) {
			extract( $args );
			$script = "<script type='text/javascript'>\njQuery(document).ready(function() {\n";
			// set up linechart object
			$lineChart = new googlyzer_ChartObj( array(
				'composite' => 		false,
				'elementId' => 		$elementid,
				'fillColor' => 		$fillcolor,
				'height' => 		$height,
				'lineColor' => 		$linecolor,
				'maxSpotColor' => 	$maxspotcolor,
				'minSpotColor' =>	$minspotcolor,
				'spotColor' =>		$spotcolor,
				'type' => 			'line',
				'values' =>			$dataObj->getMetricValuesString( 
					$metric = $metric, 
					$period = 'current', 
					$historyFlag = false 
					),
				'width' => 			$width
				) );
			// set up sparkline tags
			$lineChart->buildTags();
			$script .= $lineChart->getSparklineScript() . "\n";
			$script .= "});\n</script>";
			// insert scripts into html for output
			$html = str_replace( '<!-- script -->', $script, $html );
			return $html;
		}
		
		/**
		 * 
		 * method to retrieve list of WP post categories
		 * 
		 * @return array $categories (formatted as $categories['cat_1'] = 'category_one' )
		 */
		public function getCategories() {
			$categories = array();
			$args = array(
					 	'orderby' => 'name',
						'order' => 'ASC'
			);
			$categoryObjs = get_categories( $args );
			foreach ( $categoryObjs as $category ) {
				$categories['cat_' . $category->cat_ID] = $category->name ;
			}
			return $categories;
		}
		
		/**
		 * 
		 * method to retrieve WP comment count for a post/page by title
		 * 
		 * @param string $endDate
		 * @param string $title
		 * @param string $startDate
		 * 
		 * @return integer
		 */
		public function getCommentCountByTitle( $endDate, $title, $startDate ) {
			global $wpdb;
			$myQuery = array( 
				"SELECT COUNT(*) AS comment_count, post_title ",
				"FROM {$wpdb->prefix}posts AS t1 INNER JOIN {$wpdb->prefix}comments as t2 ON t1.ID = t2.comment_post_ID ",
				"WHERE t2.comment_date >= '" . $startDate . " 00:00:00' AND t2.comment_date <= '" . $endDate . " 23:59:59' ",
				"AND t1.post_title = '$title' ",
				"GROUP BY ID ",
				"ORDER BY comment_count DESC LIMIT 1"
				);
			$posts = $wpdb->get_results( join( '', $myQuery ) );
			foreach ( $posts as $post ) {
				return $post->comment_count;
			}
			return 0;
		}
		
		/**
		 * 
		 * method to check curl server response types
		 * 
		 * @param object $curl_obj
		 * 
		 * @return array $response
		 */
		public function getCurlInfo( $curl_obj ) {
			$response = array();
			$response['info'] = curl_getinfo( $curl_obj );
			switch ( $response['info']['http_code'] ) {
				case 200:
					$response['type'] = 'Valid request.';
					break;
				case 400:
					$response['type'] = 'Bad request.';
					break;
				case 401:
					$response['type'] = 'Unauthorized.';
					break;
				case 403:
					$response['type'] = 'Access denied.';
					break;
				case 500:
					$response['type'] = 'Internal server error.';
					break;
				case 503:
					$response['type'] = 'Bad request.';
					break;
				default:
					$response['type'] = 'Service unavailable.';
				break;
			}
			return $response;
		}
		
		/**
		 * 
		 * method to determine integer for dynamic date range selection
		 * 
		 * @param string $selection
		 * 
		 * @return integer
		 */
		public function getDateRange( $selection ) {
			$amount = 0;
			// set start date based on selected date range
			switch ( $selection ) {
				case '05 days':
					$amount = 5;
					break;
				case '10 days':
					$amount = 10;
					break;
				case '20 days':
					$amount = 20;
					break;
				case '30 days':
					$amount = 30;
					break;
				case '60 days':
					$amount = 60;
					break;
				case '90 days':
					$amount = 90;
					break;
			}
			return $amount;
		}
		
		/**
		 * 
		 * Get html from external file using file_get_contents, return html output.
		 * 
		 * @param string $target
		 * @param array $replacement_array
		 * 
		 * @return string $html
		 */
		public function getFilteredHtml( $target, $replacement_array = array() ) {
			// output linechart settings begin html
			$html = file_get_contents(
				$filename = $target,
				$use_include_path = false
				);
			if ( !$html ) {
				throw new Exception( 'googlyzer_Utilities::getFilteredHtml unable to load contents from ' . $target );
			} else {
				if ( !empty( $replacement_array ) ) {
					foreach ( $replacement_array as $key => $value ) {
						$html = str_replace( $key, $value, $html );
					}
				}
				return $html;
			}
		}
		
		/**
		 * 
		 * Method to attempt retrieval of new GA Service token.
		 * 
		 * @param array $genOpts
		 * @throws googlyzer_Exception $e
		 * @return string $ga_auth_token
		 */
		public function getNewGoogleAuthToken( $genOpts ) {
			// call ClientLogin class
			if ( class_exists( 'googlyzer_ClientLogin' ) ) {
				// instantiate clientLogin feed
				$gaClient = new googlyzer_ClientLogin(
					$feedType =					'login',
					$url =						'https://www.google.com/accounts/ClientLogin',
					$method = 					'POST',
					$feedSpecificSettings = array(
						'email' =>				$genOpts['ga_login'],
						'passwd' =>				$genOpts['ga_password'],
						'captcha_response' =>	'',
						'GA_CAPTCHA_TOKEN' => 	''
						)
					);
				// start login and process response
				try {
					// initiate login process
					$gaClient->login();
					$status = 'not_set';
					// check authorization status and process appropriately
					switch ( $gaClient->GA_AUTH_STATUS ) {
						case 'authorized':
							$status = "Authenticated.";
							break;
						case 'curl_error':
							$status = "Curl Error: " . $gaClient->getCurlError();
							break;
						case 'captcha_challenge':
							$status = "Google Error: " . $gaClient->getGoogleError();
							break;
						case 'google_error':
							$status = "Google Error: " . $gaClient->getGoogleError();
							break;
					}
					if ( $gaClient->GA_AUTH_STATUS != 'authorized' ) {
						throw new googlyzer_Exception( $status );
					} else {
						return $gaClient->GA_AUTH_TOKEN;
					}
				} catch ( googlyzer_Exception $e ) {
					throw new googlyzer_Exception( $e->getError() );
				}
			} else {
				throw new googlyzer_Exception( 'googlyzer_ClientLogin class not loaded.' );
			}
		}
		
		/**
		 * 
		 * method for retrieving a specified option (group) from WP database.
		 * 
		 * @param string $group
		 * @param string $section
		 * @return array $options
		 */
		public function getOptions( $group, $section = '' ) {
			$options = get_option(
				$show = $group,
				$default = false
				);
			if ( empty( $section ) ) {
				return $options;
			} else {
				return $options[$section];
			}
		}
		
		/**
		 * 
		 * method to search for pattern matches in a string
		 * 
		 * @param string $needle
		 * @param string $haystack
		 * 
		 * @return array $matches
		 */
		public function getPatternMatch( $needle, $haystack ) {
			if ( preg_match ( $needle, $haystack, $matches ) ) {
				return array( 'found' => true, 'matches' => $matches );
			} else {
				return array( 'found' => false );
			}
		}
		
		/**
		 * 
		 * method to retrieve list of WP post titles with the most comments (descending order).
		 * 
		 * @param string $includePages (enabled or disabled)
		 * @param string $includePosts (enabled or disabled)
		 * @param integer $numRows
		 * 
		 * @return array $rows ( formatted as $rows['post_title'] = 'post_comment_count'
		 */
		public function getPostsByCommentCount( $endDate, $includePages, $includePosts, $numRows, $period, $startDate ) {
			global $wpdb;
			$rows = array();
			$WHERE = "WHERE ";
			if ( $includePages === 'enabled' && $includePosts === 'enabled' ) {
				$WHERE .= "post_type='page' OR post_type='post' ";
			} elseif ( $includePages === 'enabled' ) {
				$WHERE .= "post_type='page' ";
			} elseif ( $includePosts === 'enabled' ) {
				$WHERE .= "post_type='post' ";
			}
			if ( $period === 'Total' ) {
				$myQuery = array( "SELECT post_title, comment_count ",
					"FROM {$wpdb->prefix}posts ",
					"$WHERE ORDER BY comment_count DESC LIMIT $numRows"
					);
				$posts = $wpdb->get_results( join( '', $myQuery ) );
			} else {
				$myQuery = array( "SELECT COUNT(*) AS comment_count, post_title ",
					"FROM {$wpdb->prefix}posts AS t1 INNER JOIN {$wpdb->prefix}comments as t2 ON t1.ID = t2.comment_post_ID ",
					"WHERE t2.comment_date >= '" . $startDate . " 00:00:00' AND t2.comment_date <= '" . $endDate . " 23:59:59' ",
					"GROUP BY ID ",
					"ORDER BY comment_count DESC LIMIT 100"
					);
				$posts = $wpdb->get_results( join( '', $myQuery ) );
			}
			foreach ( $posts as $post ) {
				$rows[$post->post_title] = $post->comment_count;
			}
			return $rows;
		}
		
		/**
		 * 
		 * Method to get excerpt of post from WordPress.
		 * 
		 * @param integer $length
		 * @param integer $postID
		 * 
		 * @return string $content
		 */
		public function getPostExcerpt( $length, $postID ) {
			global $wpdb;
			$WHERE = "WHERE ID=$postID";
			$posts = $wpdb->get_results( "SELECT post_content, ID FROM {$wpdb->prefix}posts $WHERE LIMIT 1" );
			foreach ( $posts as $post ) {
				$content = strip_tags( strip_shortcodes( $post->post_content ) );
				return googlyzer_Utilities::getStringExcerpt( $length, $content );
			}
		}
		
		/**
		 * 
		 * method to push selection keys from an associative array into a list
		 * 
		 * @param array $selections
		 * 
		 * @return array $list
		 */
		function getSelections( $selections ) {
			$list = Array();
			// check to make sure selections is an array
			if ( is_array( $selections ) ) {
				// check to make sure selections is an associative array
				if ( googlyzer_Utilities::isAssociative( $selections ) ) {
					foreach ( $selections as $key => $value ) {
						array_push( $list, $key );
					}
					return $list;
				} else {
					throw new googlyzer_Exception( 'selections list is not an associative array.' );
				}
			} else {
				throw new googlyzer_Exception( 'selections list is not a valid array.' );
			}
		}
		
		/**
		 * 
		 * Method to get excerpt (measured in # of words) of string.
		 * 
		 * @param integer $length
		 * @param string $string
		 * 
		 * @return string $output
		 */
		public function getStringExcerpt( $length, $string ) {
			$words = explode( ' ', $string );
			if ( count( $words ) > $length ) {
				return implode( ' ', array_slice( $words, 0, $length ) ) . ' ... ';
			} else {
				return $string;
			}
		}
		
		/**
		 * 
		 * method to check if array is associative
		 * 
		 * @param array $arr
		 * 
		 * @return boolean
		 */
		public function isAssociative( $arr ) {
			if ( is_array( $arr ) ) {
				foreach ( $arr as $key => $value ) {
					if ( !is_int( $key ) ) {
						return true;
					}
				}
			}
			return false;
		}
		
		/**
		 * 
		 * method to check for google service authentication token
		 * 
		 * @return boolean
		 */
		public function isAuthorized() {
			$options = get_option(
				$show = 'googlyzer-options',
				$default = false
				);
			if ( empty( $options['standard']['ga_auth_token'] ) ) {
				?>
    				<p>
    					Error: Google Authentication has not been completed.<br>
    				    Please return to the General tab on the Googlyzer Settings page, authenticate with Google, and save your settings.
    				</p>
				<?php
				return false;
			} else {
				return true;
			}
		}
		
		/**
		 * 
		 * Method to convert WP page title into hyperlink
		 * 
		 * @param string $title
		 * @return string $title
		 */
		public function linkifyTitle( $title ) {
			$url = '';
			// check for UTF-8 character set encoding in title
			if ( mb_detect_encoding( $title ) == 'UTF-8' ) {
				// convert to ISO-8859-1 character set for MySQL
				$title = iconv( "UTF-8", "ISO-8859-1//TRANSLIT", $title );
			}
			if ( $pageObj = get_page_by_title( $page_title = $title, $output = OBJECT, $post_type = 'post' ) ) {
				$url = get_permalink( $pageObj );
			} elseif ( $pageObj = get_page_by_title( $page_title = $title, $output = OBJECT, $post_type = 'page' ) ) {
				$url = get_permalink( $pageObj );
			}
			if ( !empty( $url ) ) {
				return '<a href="' . $url . '">' . $title . '</a>';
			} else {
				return $title;
			}
		}
		
		function string_diff( $old, $new ){
			$maxlen = 0;
			foreach ( $old as $oindex => $ovalue ){
				$nkeys = array_keys( $new, $ovalue );
				foreach ( $nkeys as $nindex ){
					$matrix[$oindex][$nindex] = isset( $matrix[$oindex - 1][$nindex - 1] ) ?
					$matrix[$oindex - 1][$nindex - 1] + 1 : 1;
					if ( $matrix[$oindex][$nindex] > $maxlen ){
						$maxlen = $matrix[$oindex][$nindex];
						$omax = $oindex + 1 - $maxlen;
						$nmax = $nindex + 1 - $maxlen;
					}
				}
			}
			if ( $maxlen == 0 ) return array( array( 'd'=>$old, 'i'=>$new ) );
			return array_merge(
					googlyzer_Utilities::string_diff( array_slice( $old, 0, $omax ),
							array_slice( $new, 0, $nmax ) ),
					array_slice( $new, $nmax, $maxlen ),
					googlyzer_Utilities::string_diff( array_slice($old, $omax + $maxlen ),
							array_slice( $new, $nmax + $maxlen ) ) );
		}
		
		function htmlDiff( $old, $new ){
			$ret = '';
			$diff = googlyzer_Utilities::string_diff( explode( ' ', $old ), explode( ' ', $new ) );
			foreach( $diff as $k ){
				if ( is_array( $k ) )
					$ret .= ( !empty($k['d'] ) ? "<del>" .
							implode( ' ',$k['d'] ) . "</del> ":'' ) .
							( !empty($k['i'] ) ? "<ins>" . implode( ' ',$k['i'] ) . "</ins> ":'' );
				else $ret .= $k . ' ';
			}
			return $ret;
		}
		
		/**
		*
		* method for retrieving a desired date
		*
		* @param array $args
		* 
		* @return string $dateOut
		*/
		public function newDateString( $args ) {
			// set timezone
			date_default_timezone_set( 'UTC' );
			extract( $args );
			if ( !isset( $now ) ) {
				$now = strtotime( 'now' );
			} else {
				if ( !googlyzer_Utilities::validDate( $now, 'Y-m-d' ) ) {
						googlyzer_Utilities::convertDateString( $now, 'ga_request' );
				}
				$now = strtotime( $now );
			}
			switch ( $change ) {
				case 'decrease':
					if ( $amount > 0 ) {
						$now = strtotime( date( 'Y-m-d', $now ) . " -$amount days" );
					}
					break;
				case 'increase':
					if ( $amount > 0 ) {
						$now = strtotime( date( 'Y-m-d', $now ) . " +$amount days" );
					}
					break;
			}
			switch ( $format ) {
				case 'ga_request':
					$dateOut = date( 'Y-m-d', $now );
					break;
				case 'googlyzer':
					$dateOut = date( 'm-d-Y', $now );
					break;
			}
			return $dateOut;
		}
		
		/**
		 * 
		 * method to sort object based on specified sort type and order
		 * 
		 * @param object $myObj
		 * @param string $sortOrder
		 * @param string $sortType
		 * 
		 * @return object $sortedObj
		 */
		public function objSort( $myObj, $sortOrder, $sortType = 'numeric' ) {
			$sortFlags = array( 'numeric' => SORT_NUMERIC, 'regular' => SORT_REGULAR, 'string' => SORT_STRING );
			$sortList = array();
			$sortedObj = array();
			// first build array with basekeys => value
			foreach ( $myObj as $key => $value ) {
				$sortList[$key] = $value;
			}
			// perform sort with selected options on new array
			switch ( $sortOrder ) {
				case 'ascending':
					asort( $sortList, $sort_flags = $sortFlags[$sortType] );
					break;
				case 'descending':
					arsort( $sortList, $sort_flags = $sortFlags[$sortType] );
					break;
			}
			// rebuild object based on sorted list
			foreach ( $sortList as $key => $value ) {
				$sortedObj[$key] = $value;
			}
			return $sortedObj;
		}
		
		/**
		*
		* public method to output curl response info
		* 
		* @param array $response (curl info)
		* 
		* @return string $out;
		*/
		public function outputCurlInfo( $response ) {
			$out = "<p>" . $response['type'] . "</p>";
			$out .= "<p>" . $response['info']['http_code'] . "</p>";
			return $out;
		}
		
		/**
		 * 
		 * public function to trim symbols ( » and « ) and site name from page title string
		 * 
		 * @param string $pageTitleString
		 * 
		 * @return string $trimmedString
		 */
		public function pageTitleFilter( $pageTitleString ) {
			$trimmedString = $pageTitleString;
			// check type and existence of page title and site name join
			if ( preg_match( "/»/", $pageTitleString ) ) {
				// trim » from site name
				$strArray = preg_split( "/»/", $pageTitleString );
				$trimmedString = trim( array_shift( $strArray ) );
				$trimmedString = substr( $trimmedString, 0, strlen( $trimmedString ) -2 );
			} else if ( preg_match( "/«/", $pageTitleString ) ) {
				// trim « from site name
				$strArray = preg_split( "/«/", $pageTitleString );
				$trimmedString = trim( array_shift( $strArray ) );
				$trimmedString = substr( $trimmedString, 0, strlen( $trimmedString ) -2 );
			} else if ( preg_match( "/\|/", $pageTitleString ) ) {
				// trim | from site name
				$strArray = preg_split( "/\|/", $pageTitleString );
				$trimmedString = trim( array_shift( $strArray ) );
				$trimmedString = substr( $trimmedString, 0, strlen( $trimmedString ) );
			}
			// return trimmed page title
			return $trimmedString;
		}
		
		/**
		*
		* Method to record sparkline metadata in html output.
		*
		* @param object $dataObj
		* @param string $html
		* @param string $metric
		* @param string $period
		*
		* @return string $html
		*/
		function recordMetaData( $dataObj, $html, $metric ) {
			$total = $dataObj->getMetaDataVal( 'total', $metric, 'current' );
			$html = str_replace( '<!-- metadata -->', $total, $html );
			return $html;
		}
		
		/**
		 * 
		 * method to round a list of values to specified # of decimal places. 
		 * will only round values with existing decimal places.
		 * 
		 * @param array $myValuesArray
		 * @param integer $decimals
		 * 
		 * @return array $myValuesArray
		 */
		public function roundAssociativeArrayVals( $myValuesArray, $decimals ) {
			foreach ( $myValuesArray as $key => $value ) {
				if ( strpos( $value, "." ) ) {
					$myValuesArray[$key] = round( $value, $decimals );
				}
			}
			return $myValuesArray;
		}
		
		/**
		 * 
		 * method to set dates within args array for GA data feed request
		 * 
		 * @param array $args
		 * @param string $type
		 * @param array $genOpts
		 * @param array $chartOpts
		 * 
		 * @return array $args
		 */
		public function setDates( $args, $type, $genOpts, $chartOpts ) {
			// check to see if including partial data from today, or if custom range is selected
			if ( $genOpts['date_range'] === 'Custom Range' ) {
				// check to see if chart type is linechart and we're including history
				if ( preg_match( "/line/", $type ) && $chartOpts['history_enable'] === 'enabled' ) {
					// if we're including history then we need to find the difference in days between the dates
					$delta = googlyzer_Utilities::dateDiff( $genOpts['start'], $genOpts['end'] );
					// then retrieve start date using an expanded range (2x)
					$args['start'] = googlyzer_Utilities::newDateString( array(
						'now' =>		googlyzer_Utilities::convertDateString( $genOpts['start'], 'ga_request' ),
						'change'=>		'decrease', 
						'amount'=>		abs( $delta ), 
						'format'=>		'ga_request' 
						) );
					// end date does not change
					$args['end'] = googlyzer_Utilities::convertDateString( $genOpts['end'], 'ga_request' );
					
				} else {
					$args['start'] = googlyzer_Utilities::convertDateString( $genOpts['start'], 'ga_request' );
					$args['end'] = googlyzer_Utilities::convertDateString( $genOpts['end'], 'ga_request' );
				}
			} else {
				// check to see if including partial data from today
				if ( $genOpts['today_enable'] === 'enabled' ) {
					$startAmount = 0;
					$endAmount = 0;
				} else {
					// if not including partial data from today, set back start and end dates by 1 day
					$startAmount = 1;
					$endAmount = 1;
				}
				$startAmount += googlyzer_Utilities::getDateRange( $genOpts['date_range'] );
				// check to see if chart type is linechart and we're including history
				if ( preg_match( "/line/", $type ) && $chartOpts['history_enable'] === 'enabled' ) {
					// then retrieve start date using an expanded range (2x)
					$args['start'] = googlyzer_Utilities::newDateString( array(
						'change'=>		'decrease', 
						'amount'=>		$startAmount*2, 
						'format'=>		'ga_request' 
						) );
				} else {
					// retrieve standard start date
					$args['start'] = googlyzer_Utilities::newDateString( array(
						'change'=>		'decrease', 
						'amount'=>		$startAmount, 
						'format'=>		'ga_request' 
						) );
				}
				// retrieve end date
				$args['end'] = googlyzer_Utilities::newDateString( array(
					'change'=>		'decrease', 
					'amount'=>		$endAmount, 
					'format'=>		'ga_request'
					) );
			}
			return $args;
		}
		
		/**
		*
		* Method to set a metric or dimension filter for Google Analytics data feed request.
		*
		* @param array $args -- associative array of arguments used to build GA data feed request
		* @param string $ga_name -- name of GA dimension or metric in filter, e.g. 'ga:pageviews'
		* @param string $needle -- string or numeric value to be used in operation statement
		* @param string $operation -- string key for operation, e.g. 'contains_substring'
		* @param string $type -- either 'dimensions' or 'metrics', depending upon type of filter
		* @throws googlyzer_Exception $e -- use try/catch block with $e->getError() to retrieve error message
		* @return array $args -- associative array of arguments used to build GA data feed request
		*/
		public function setGoogleAnalyticsRequestFilter( $args, $ga_name, $needle, $operation, $type ) {
			// list of possible filter operations
			$ops = array(
				'dimensions' => array(
					'contains_substring' =>				'=@',
					'does_not_contain_substring' =>		'!@',
					'exact_match' =>					'==',
					'no_match' =>						'!=',
					'regex_match_start' =>				'=~^',
					'regex_match' =>					'=~',
					'regex_no_match' =>					'!~'
					),
				'metrics' => array(
					'equals' =>							'==',
					'does_not_equal' =>					'!=',
					'greater_than' =>					'>',
					'greater_than_or_equal_to' =>		'>=',
					'less_than' =>						'<',
					'less_than_or_equal_to' =>			'<='
					)
				);
			if ( array_key_exists( $operation, $ops[$type] ) ) {
				// add filter target to dimensions list, if not already there
				if ( array_key_exists( $type, $args) && !in_array( $ga_name, $args[$type] ) )  {
					array_push( $args[$type], $ga_name );
				}
				// retrieve current page title from WP without separator and
				// set page filter argument for feed args
				if ( array_key_exists( 'filters', $args ) && !empty( $args['filters'] ) ) {
					/*
					 * @todo: validate that filter key contains a valid GA filter definition if it is not empty
					 * 		For now it will be assumed if the filter key is non-empty, that it contains a valid GA 
					 * 		filter definition.
					 */
					$args['filters'] .= ';' . $ga_name . $ops[$type][$operation] . $needle;
				} else {
					$args['filters'] = $ga_name . $ops[$type][$operation] . $needle;
				}
				// return modified feed args
				return $args;
			} else {
				throw new googlyzer_Exception( "$operation is not a valid Google Analytics $type filter operation." );
			}
		}
		
		/**
		 * 
		 * method to return sort order for Google Analytics data feed request
		 * 
		 * @param string $sort
		 * 
		 * @return string
		 */
		public function setSortOrder( $sortOrder, $sortColumn ) {
			$sort = ( $sortOrder === 'descending' ) ? '-' : '';
			$sort .= $sortColumn;
			return $sort;
		}
		
		/**
		 * 
		 * method to validate proper Googlyzer set up
		 * 
		 * @return boolean
		 */
		public function validateSetup() {
			if ( !googlyzer_Utilities::isAuthorized() ) {
				return false;
			} elseif ( !googlyzer_Utilities::domainSaved() ) {
				return false;
			} else {
				return true;
			}
		}
		
		/**
		 * 
		 * method to validate proper check box values
		 * 
		 * @param string $value
		 * 
		 * @return boolean
		 */
		function validCheckbox( $value ) {
			if ( $value !== 'enabled' && $value !== 'disabled' ) {
				return false;
			} else {
				return true;
			}
		} // end method validCheckbox
		
		/**
		*
		* method to validate proper date string
		*
		* @param string $date
		* @param string $dateFormat (Y-m-d or m-d-Y)
		*
		* @return boolean
		*/
		function validDate( $date, $style ) {
			if ( $style === 'Y-m-d' && !empty( $date ) ) {
				$dateFormat = '/^\d{4}[\-]\d{2}[\-]\d{2}$/';
				list( $year, $month, $day ) = explode( "-", $date );
			} elseif ( $style === 'm-d-Y' && !empty( $date ) ) {
				$dateFormat = '/^\d{2}[\-]\d{2}[\-]\d{4}$/';
				list( $month, $day, $year ) = explode( "-", $date );
			} else { // otherwise unrecognized date format
				return false;	
			}
			// if date format is bad, return false
			if ( preg_match( $dateFormat, $date ) !== 1 ) {
				return false;
				// otherwise date format is valid, proceed with detailed check
			} else {
				// check to make sure date is valid and not before 2005
				if ( !checkdate( $month, $day, $year ) || $year < 2005 ) {
					return false;
				}
			}
			return true;
		} // end method validDate
		
		/**
		 * 
		 * Method to validate date range input.
		 * 
		 * @param string $end
		 * @param string $start
		 * 
		 * @return array
		 */
		function validDateRange( $end, $start ) {
			// set timezone
			date_default_timezone_set( 'UTC' );
			if ( !googlyzer_Utilities::validDate( $end, 'Y-m-d' ) ) {
				if ( googlyzer_Utilities::validDate( $end, 'm-d-Y' ) ) {
					googlyzer_Utilities::convertDateString( $end, 'ga_request' );
				} else {
					$end = googlyzer_Utilities::defaultEndDate();
				}
			}
			if ( !googlyzer_Utilities::validDate( $start, 'Y-m-d' ) ) { 
				if ( googlyzer_Utilities::validDate( $start, 'm-d-Y' ) ) {
					googlyzer_Utilities::convertDateString( $start, 'ga_request' );
				} else {
					$start = googlyzer_Utilities::newDateString( array(
						'amount' => 30,  
						'change' => 'decrease',
						'format' => 'ga_request',
						'now' =>	$end
						) );
				}
			}
  			// check for invalid date range
			if ( strtotime( $start ) > strtotime( $end ) ) {
				$end = googlyzer_Utilities::defaultEndDate();
				$start = googlyzer_Utilities::defaultStartDate();
			}
			return array( 'end' => $end, 'start' => $start );
		}
		
		/**
		 * 
		 * Method to make sure 'px' is added to chart dimensions.
		 * 
		 * @param array $atts
		 * 
		 * @return array $atts
		 */
		function validDimensions( $atts ) {
			if ( !preg_match( "/px/", $atts['height'] ) ) {
				$atts['height'] .= 'px';
			}
			if ( !preg_match( "/px/", $atts['width'] ) ) {
				$atts['width'] .= 'px';
			}
			return $atts;
		}
		
		/**
		 * 
		 * Method to validate list of GA metrics or dimensions.
		 * @param multi-type: array, string $fieldList
		 * 
		 * @return boolean
		 */
		function validGAField( $fieldList ) {
			if ( is_array( $fieldList ) ) {
				foreach ( $fieldList as $key => $field ) {
					if ( !checkGAField( $field ) ) {
						return false;
					}
				}
			} else {
				if ( !checkGAField( $field ) ) {
					return false;
				}
			}
			return true;
		}
		
		/**
		 * 
		 * method to validate proper hex color value
		 * 
		 * @param string $color
		 * 
		 * @return boolean
		 */
		function validHexColor( $color ) {
			if ( !preg_match( "/^#[a-f0-9]{6}$/i", $color ) ) {
				return false;
			} else {
				return true;
			}
		} // end method validHexColor
		
		/**
		 * 
		 * method to validate proper Google Analytics Table Id
		 * 
		 * @param string $ga_tableid
		 * 
		 * @return boolean
		 */
		function validGoogleAnalyticsTableId( $ga_tableid ) {
			if ( !preg_match( "/^ga:[0-9]{5,10}/", $ga_tableid ) ) {
				return false;
			} else {
				return true;
			}
		} // end method validGoogleAnalyticsTableId
		
		/**
		 * 
		 * Method to make sure desired start date for GA data feed request is not prior to first occurrence of GA data
		 * for selected domain profile.
		 * 
		 * @param string $dataStart
		 * @param string $startDate
		 * 
		 * @return string
		 */
		function validStart( $dataStart, $startDate ) {
			if ( !googlyzer_Utilities::validDate( $startDate, 'Y-m-d' ) ) {
				$startDate = googlyzer_Utilities::convertDateString( $startDate, 'ga_request' );
			}
			// set timezone
			date_default_timezone_set( 'UTC' );
			// if start is prior to beginning of GA data, use date for first occurrence of GA data
			if ( strtotime( $startDate ) < strtotime( $dataStart ) ) {
				return $dataStart;
				// otherwise, use requested start date
			} else {
				return $startDate;
			}
		}
		
	} // end class googlyzer_Utilities
	
} // end if class exists