<?php
namespace JExtstore\Component\JRealtimeAnalytics\Administrator\Model;
/**
 *
 * @package JREALTIMEANALYTICS::GOOGLE::administrator::components::com_jrealtimeanalytics
 * @subpackage models
 * @author Joomla! Extensions Store
 * @copyright (C) 2014 - Joomla! Extensions Store
 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
 */
defined ( '_JEXEC' ) or die ( 'Restricted access' );
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use Joomla\String\StringHelper;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Model as JRealtimeModel;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Http;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Http\Transport\Socket;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Http\Transport\Curl;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Http\Response;
use JExtstore\Component\JRealtimeAnalytics\Administrator\Framework\Exception as JRealtimeException;

/**
 * Google model responsibilities for access Google Analytics and Webmasters Tools API
 *
 * @package JREALTIMEANALYTICS::SOURCES::administrator::components::com_jrealtimeanalytics
 * @subpackage models
 * @since 3.1
 */
interface IModelGoogle {
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataAnalytics();
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataReporting();
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataData();

	/**
	 * Main get data method for Matomo dashboard
	 *
	 * @access public
	 * @return mixed
	 */
	public function getDataMatomo();
	
	/**
	 * Return the google token
	 *
	 * @access public
	 * @return string
	 */
	public function getToken();
}

/**
 * Sources model concrete implementation <<testable_behavior>>
 *
 * @package JREALTIMEANALYTICS::GOOGLE::administrator::components::com_jrealtimeanalytics
 * @subpackage models
 * @since 3.1
 */
class GoogleModel extends JRealtimeModel implements IModelGoogle {
	/**
	 * Google_Client object
	 *
	 * @access private
	 * @var Google_Client
	 */
	private $client;
	
	/**
	 * Current profile found for Google Analytics
	 *
	 * @access private
	 * @var string
	 */
	private $currentProfile;
	
	/**
	 * Track the API connection mode, built in JSitemap Google App or user own
	 *
	 * @access private
	 * @var string
	 */
	private $hasOwnCredentials;
	
	/**
	 * Get the top level host domain for each kind of URL needed to avoid Alexa redirects on CURL exec
	 *
	 * @access private
	 * @param string $url
	 * @return string
	 */
	private function getHost($url) {
		if (strpos ( $url, "http" ) !== false) {
			$httpurl = $url;
		} else {
			$httpurl = "http://" . $url;
		}
		$parse = parse_url ( $httpurl );
		$domain = $parse ['host'];
		
		$parts = explode ( ".", $domain );
		$count = sizeof ( $parts ) - 1;
		
		if ($count > 1) {
			$slicedParts = array_slice( $parts, -2, 1 );
			$slice = ( strlen( reset( $slicedParts ) ) == 2 || in_array(reset( $slicedParts ), array('com', 'org', 'gov', 'net'))) && ( count( $parts ) > 2 ) ? 3 : 2;
			$result = implode( '.', array_slice( $parts, ( 0 - $slice ), $slice ) );
		} else {
			$result = $domain;
		}
		return $result;
	}
	
	/**
	 * Get the title string based on the gaquery selection
	 *
	 * @access private
	 * @param string $gaperiod
	 * @return string
	 */
	private function getTitle($gaquery) {
		switch ($gaquery) {
			case 'totalUsers':
			case 'users' :
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_VISITORS' );
				break;
				
			case 'screenPageViews':
			case 'pageviews' :
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_PAGE_VIEWS' );
				break;
				
			case 'engagementRate':
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_ENGAGEMENTRATE' );
				break;
				
			case 'sessionsPerUser':
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_SESSIONSPERUSER' );
				break;
				
			case 'bounceRate' :
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_BOUNCE_RATE' );
				break;
				
			case 'organicSearches' :
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_ORGANIC_SEARCHES' );
				break;
				
			default :
				$title = Text::_ ( 'COM_JREALTIME_GOOGLE_VISITS' );
		}
		
		return $title;
	}
	
	/**
	 * Get the period array based on the gaperiod selection
	 *
	 * @access private
	 * @param string $gaperiod
	 * @param boolean $isDataApi
	 * @return array
	 */
	private function getPeriod($gaperiod, $isDataApi = false) {
		$periodArray = array();
		
		switch ($gaperiod) {
			case 'today' :
				if($isDataApi) {
					$periodArray = array("from" => date ( 'Y-m-d', time () - 24 * 60 * 60 ),
							"to" => date ( 'Y-m-d' ),
							"showevery" => 1);
				} else {
					$periodArray = array("from" => date ( 'Y-m-d' ),
							"to" => date ( 'Y-m-d' ),
							"showevery" => 2);
				}
				break;
				
			case 'yesterday' :
				if($isDataApi) {
					$periodArray = array("from" => date ( 'Y-m-d', time () - 48 * 60 * 60 ),
							"to" => date ( 'Y-m-d', time () - 24 * 60 * 60 ),
							"showevery" => 1);
				} else {
					$periodArray = array("from" => date ( 'Y-m-d', time () - 24 * 60 * 60 ),
							"to" => date ( 'Y-m-d', time () - 24 * 60 * 60 ),
							"showevery" => 2);
				}
				break;
				
			case 'last7days' :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 7 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 1);
				break;
				
			case 'last14days' :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 14 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 2);
				break;
				
			case 'last3months' :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 90 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 7);
				break;
				
			case 'last6months' :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 180 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 15);
				break;
				
			case 'last12months' :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 365 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 30);
				break;
				
			case 'last30days' :
			default :
				$periodArray = array("from" => date ( 'Y-m-d', time () - 30 * 24 * 60 * 60 ),
				"to" => date ( 'Y-m-d' ),
				"showevery" => 3);
				break;
		}
		
		return $periodArray;
	}
	
	/**
	 * Purify and normalize domain protocol
	 *
	 * @access private
	 * @return string
	 */
	private function purifyDomain($domain) {
		if($this->getComponentParams ()->get('ga_domain_match_protocol', 0)) {
			return $domain;
		}
		return str_replace ( array (
				"https://",
				"http://",
				" "
		), "", rtrim ( $domain, "/" ) );
	}
	
	/**
	 * Purify and normalize domain uri for webmasters tools stats
	 *
	 * @access private
	 * @return string
	 */
	private function purifyWebmastersDomain($domain) {
		return str_replace ( array (
				" "
		), "", rtrim ( $domain, "/" ) );
	}
	
	/**
	 * Manage the authentication form and action
	 *
	 * @param Object $params
	 * @access private
	 * @return mixed A string when auth is needed, null if performing an auth
	 */
	private function authentication($params) {
		$this->client = new \Google_Client ();
		$this->client->setAccessType ( 'offline' );
		$this->client->setScopes ( array( 'https://www.googleapis.com/auth/analytics.readonly', 'https://www.googleapis.com/auth/webmasters' ));
		$this->client->setApplicationName ( 'JRealtime Analytics' );
		$this->client->setRedirectUri ( 'https://storejextensions.org/oauthauthentication.html' );
		$this->client->setPrompt("consent");
		
		$this->hasOwnCredentials = false;
		if ($params->get ( 'ga_api_key' ) and $params->get ( 'ga_client_id' ) and $params->get ( 'ga_client_secret' )) {
			$this->client->setClientId ( $params->get ( 'ga_client_id' ) );
			$this->client->setClientSecret ( $params->get ( 'ga_client_secret' ) );
			$this->client->setDeveloperKey ( $params->get ( 'ga_api_key' ) ); // API key
			$this->hasOwnCredentials = true;
		} else {
			$this->client->setClientId ( '872567856644-hqr0nu4a5hhf9opbjgmdn1giu055823b.apps.googleusercontent.com' );
			$this->client->setClientSecret ( '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' );
			$this->client->setDeveloperKey ( 'AIzaSyC-4AcgDEPvXZOJ1KMZky7VgOfz4QrQKyc' );
		}
		
		try {
			if ($this->getToken ()) { // extract token from session and configure client
				$token = $this->getToken ();
				$this->client->setAccessToken ( $token );
			}
		} catch ( \Exception $e ) {
			$jrealtimeException = new JRealtimeException ( $e->getMessage (), 'error' );
			$this->app->enqueueMessage ( $jrealtimeException->getMessage (), $jrealtimeException->getExceptionLevel () );
			return '<a class="btn btn-primary" href="index.php?option=com_jrealtimeanalytics&amp;task=google.deleteEntity">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LOGOUT' ) . '</a>';
		}
		
		if (! $result = $this->client->getAccessToken ()) { // auth call to google
			$authUrl = $this->client->createAuthUrl ();
			// Trying to authenticate?
			if (!$this->app->getInput()->get('ga_dash_authorize')) {
				$JoomlaCMSLanguageText = '\Joomla\CMS\Language\Text';
				$htmlSnippet = <<<HTML
					<div id="google-authentication">
						<div class="card w-50 p-3 well">
							<div class="card-block">
								<span class="badge bg-primary">
									{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_STEP1_CODE_DESC' )}
								</span>
		  						<a class="btn btn-success btn-sm hasPopover google" role="button" data-bs-content="{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_CODE_INSTUCTIONS' )}" href="$authUrl" target="_blank">
		  							{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_CODE' )}
		  						</a>
	  						</div>
  						</div>
  						
  						<div class="card w-50 p-3 well">
  							<div class="card-block">
	  							<span class="badge bg-primary">
	  								{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_STEP2_ACCESS_CODE_INSERT' )}
	  							</span>
	  							<input type="text" name="ga_dash_code" value="" size="61">
  							</div>
  						</div>
  						
  						<div class="card w-50 p-3 well">
  							<div class="card-block">
	  							<span class="badge bg-primary">
	  								{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_STEP3_AUTHENTICATE' )}
	  							</span>
								<input type="submit" class="btn btn-success btn-sm waiter" role="button" name="ga_dash_authorize" value="{$JoomlaCMSLanguageText::_ ( 'COM_JREALTIME_GOOGLE_AUTHENTICATE' )}"/>
							</div>
						</div>
					</div>
					
HTML;
	  			return $htmlSnippet;
			} else {
				// Yes! This is an authentication attempt let's try it
				try {
					$this->client->authenticate ( $this->app->getInput()->getString('ga_dash_code'));
				} catch ( JRealtimeException $e ) {
					$this->app->enqueueMessage ( $e->getMessage (), $e->getExceptionLevel () );
					return '<a class="btn btn-primary" href="index.php?option=com_jrealtimeanalytics&amp;task=google.display">' . Text::_ ( 'COM_JREALTIME_GOBACK' ) . '</a>';
				} catch ( \Exception $e ) {
					$jrealtimeException = new JRealtimeException ( $e->getMessage (), 'error' );
					$this->app->enqueueMessage ( $jrealtimeException->getMessage (), $jrealtimeException->getExceptionLevel () );
					return '<a class="btn btn-primary" href="index.php?option=com_jrealtimeanalytics&amp;task=google.display">' . Text::_ ( 'COM_JREALTIME_GOBACK' ) . '</a>';
				}
				
				// Store the Google token in the DB for further login and authentication
				$this->storeToken ( $this->client->getAccessToken () );
				
				return null;
			}
		}
	}
	
	/**
	 * Store the Google token
	 *
	 * @access private
	 * @return boolean
	 */
	private function storeToken($token) {
		// Ensure that a token is present
		if(!$token) {
			return false;
		}
		
		// Ensure that only 1 record per each client exists in the database
		if($this->getToken()) {
			$this->deleteToken();
		}
		
		$clientID = (int)$this->app->getClientId();
		try {
			$dbToken = is_array($token) ? json_encode($token) : $token;
			$query = "INSERT IGNORE INTO #__realtimeanalytics_google (id, token) VALUES ($clientID," .  $this->dbInstance->quote($dbToken) . ")";
			$this->dbInstance->setQuery ( $query );
			$result = $this->dbInstance->execute ();
			
			// Store logged in status in session
			$session = $this->app->getSession();
			$session->set('jrealtime_ga_authenticate', true);
		} catch ( JRealtimeException $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), $e->getExceptionLevel () );
			$result = false;
		} catch ( \Exception $e ) {
			$jrealtimeException = new JRealtimeException ( $e->getMessage (), 'error' );
			$this->app->enqueueMessage ( $jrealtimeException->getMessage (), $jrealtimeException->getExceptionLevel () );
			$result = false;
		}
		
		return $result;
	}
	
	/**
	 * Delete the Google token
	 *
	 * @access private
	 * @return boolean
	 */
	private function deleteToken() {
		$clientID = (int)$this->app->getClientId();
		try {
			$query = "DELETE FROM #__realtimeanalytics_google WHERE id = " . $clientID;
			$this->dbInstance->setQuery ( $query )->execute();
			
			// Store logged in status in session
			$session = $this->app->getSession();
			$session->remove('jrealtime_ga_authenticate');
		} catch ( JRealtimeException $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), $e->getExceptionLevel () );
			return false;
		} catch ( \Exception $e ) {
			$jrealtimeException = new JRealtimeException ( $e->getMessage (), 'error' );
			$this->app->enqueueMessage ( $jrealtimeException->getMessage (), $jrealtimeException->getExceptionLevel () );
			return false;
		}
		
		return true;
	}
	
	/**
	 * Get visits by country
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsVisitsByCountry($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:country';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		for($i = 0; $i < $data ['totalResults']; $i ++) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "'," . $data ['rows'] [$i] [1] . "],";
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get Traffic Sources
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsTrafficSources($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:medium';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		for($i = 0; $i < $data ['totalResults']; $i ++) {
			$ga_dash_data .= "['" . addslashes ( str_replace ( "(none)", "direct", $data ['rows'] [$i] [0] ) ) . "'," . $data ['rows'] [$i] [1] . "],";
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get New vs. Returning
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsNewReturnVisitors($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:userType';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		for($i = 0; $i < $data ['totalResults']; $i ++) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "'," . $data ['rows'] [$i] [1] . "],";
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get Top Pages
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsTopPages($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:pageviews';
		$dimensions = 'ga:pageTitle';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions,
					'sort' => '-ga:pageviews',
					'max-results' => $params->get('ga_num_results', 24),
					'filters' => 'ga:pagePath!=/'
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		$i = 0;
		while ( isset ( $data ['rows'] [$i] [0] ) ) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "'," . $data ['rows'] [$i] [1] . "],";
			$i ++;
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get Top referrers
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsTopReferrers($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:source,ga:medium';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions,
					'sort' => '-ga:sessions',
					'max-results' => $params->get('ga_num_results', 24),
					'filters' => 'ga:medium==referral'
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		$i = 0;
		while ( isset ( $data ['rows'] [$i] [0] ) ) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "'," . $data ['rows'] [$i] [2] . "],";
			$i ++;
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get Top searches
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsTopSearches($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:keyword';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions,
					'sort' => '-ga:sessions',
					'max-results' => $params->get('ga_num_results', 24),
					'filters' => 'ga:keyword!=(not provided);ga:keyword!=(not set)'
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		$i = 0;
		while ( isset ( $data ['rows'] [$i] [0] ) ) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "'," . $data ['rows'] [$i] [1] . "],";
			$i ++;
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get Top systems
	 *
	 * @access private
	 * @return array
	 */
	private function getAnalyticsTopSystems($service, $projectId, $from, $to, $params) {
		$metrics = 'ga:sessions';
		$dimensions = 'ga:browser,ga:operatingSystem';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions,
					'sort' => '-ga:sessions',
					'max-results' => $params->get('ga_num_results', 24)
			) );
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (! $data ['rows']) {
			return false;
		}
		
		$ga_dash_data = "";
		$i = 0;
		while ( isset ( $data ['rows'] [$i] [0] ) ) {
			$ga_dash_data .= "['" . addslashes ( $data ['rows'] [$i] [0] ) . "','" . addslashes ($data ['rows'] [$i] [1]) . "'," . $data ['rows'] [$i] [2] . "],";
			$i ++;
		}
		
		return $ga_dash_data;
	}
	
	/**
	 * Get sessions visits
	 *
	 * @access private
	 * @param Google_Service_AnalyticsReporting $serviceReporting
	 * @param Google_Service_AnalyticsReporting_ReportRequest $request
	 * @param string $dimension
	 *
	 * @return string
	 */
	private function getReportingSessionMetric($serviceReporting, $request, $dimension) {
		$sessions = new \Google_Service_AnalyticsReporting_Metric();
		$sessions->setExpression("ga:sessions");
		$sessions->setAlias('sessions');
		
		$dimensionsObject = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsObject->setName($dimension);
		$dimensionsArray = array($dimensionsObject);
		
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			if($this->getComponentParams()->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (!$rows) {
			return false;
		}
		
		$gadash_data = "";
		for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
			$row = $rows[ $rowIndex ];
			$rowMetrics = $row->getMetrics();
			$rowDimensions = $row->getDimensions();
			$rowValues = $rowMetrics[0]->getValues();
			$gadash_data .= "['" . $rowDimensions [0] . "'," . round ( $rowValues [0], 2 ) . "],";
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get Top referrers
	 *
	 * @access private
	 * @param Google_Service_AnalyticsReporting $serviceReporting
	 * @param Google_Service_AnalyticsReporting_ReportRequest $request
	 *
	 * @return string
	 */
	private function getReportingTopReferrers($serviceReporting, $request) {
		$params = $this->getComponentParams ();
		
		$sessions = new \Google_Service_AnalyticsReporting_Metric();
		$sessions->setExpression("ga:sessions");
		$sessions->setAlias('sessions');
		
		$dimensionsSource = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsSource->setName('ga:source');
		$dimensionsMedium = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsMedium->setName('ga:medium');
		$dimensionsArray = array($dimensionsSource, $dimensionsMedium);
		
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		// Add criteria, max results
		$request->setPageSize($params->get('ga_num_results', 24));
		
		// Add criteria, sort ordering
		$ordering = new \Google_Service_AnalyticsReporting_OrderBy();
		$ordering->setSortOrder('DESCENDING');
		$ordering->setFieldName("ga:sessions");
		$request->setOrderBys(array($ordering));
		
		// Add criteria, filters
		// Create the DimensionFilter.
		$dimensionFilter = new \Google_Service_AnalyticsReporting_DimensionFilter();
		$dimensionFilter->setDimensionName('ga:medium');
		$dimensionFilter->setOperator('EXACT');
		$dimensionFilter->setExpressions(array('referral'));
		// Create the DimensionFilterClauses
		$dimensionFilterClause = new \Google_Service_AnalyticsReporting_DimensionFilterClause();
		$dimensionFilterClause->setFilters(array($dimensionFilter));
		$request->setDimensionFilterClauses(array($dimensionFilterClause));
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (!$rows) {
			return false;
		}
		
		$gadash_data = "";
		for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
			$row = $rows[ $rowIndex ];
			$rowMetrics = $row->getMetrics();
			$rowDimensions = $row->getDimensions();
			$rowValues = $rowMetrics[0]->getValues();
			$gadash_data .= "['" . addslashes ( $rowDimensions [0] ) . "'," . $rowValues[0] . "],";
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get Top searches
	 *
	 * @access private
	 * @return array
	 */
	private function getReportingTopSearches($serviceReporting, $request) {
		$params = $this->getComponentParams ();
		
		$sessions = new \Google_Service_AnalyticsReporting_Metric();
		$sessions->setExpression("ga:sessions");
		$sessions->setAlias('sessions');
		
		$dimensionsKeyword = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsKeyword->setName('ga:keyword');
		$dimensionsArray = array($dimensionsKeyword);
		
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		// Add criteria, max results
		$request->setPageSize($params->get('ga_num_results', 24));
		
		// Add criteria, sort ordering
		$ordering = new \Google_Service_AnalyticsReporting_OrderBy();
		$ordering->setSortOrder('DESCENDING');
		$ordering->setFieldName("ga:sessions");
		$request->setOrderBys(array($ordering));
		
		// Add criteria, filters
		// Create the DimensionFilter.
		$dimensionFilter = new \Google_Service_AnalyticsReporting_DimensionFilter();
		$dimensionFilter->setDimensionName('ga:keyword');
		$dimensionFilter->setNot(true);
		$dimensionFilter->setOperator('IN_LIST');
		$dimensionFilter->setExpressions(array('(not provided)', '(not set)'));
		// Create the DimensionFilterClauses
		$dimensionFilterClause = new \Google_Service_AnalyticsReporting_DimensionFilterClause();
		$dimensionFilterClause->setFilters(array($dimensionFilter));
		$request->setDimensionFilterClauses(array($dimensionFilterClause));
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (!$rows) {
			return false;
		}
		
		$gadash_data = "";
		for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
			$row = $rows[ $rowIndex ];
			$rowMetrics = $row->getMetrics();
			$rowDimensions = $row->getDimensions();
			$rowValues = $rowMetrics[0]->getValues();
			$gadash_data .= "['" . addslashes ( $rowDimensions [0] ) . "'," . $rowValues[0] . "],";
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get Top Pages
	 *
	 * @access private
	 * @return array
	 */
	private function getReportingTopPages($serviceReporting, $request) {
		$params = $this->getComponentParams ();
		
		$pageViews = new \Google_Service_AnalyticsReporting_Metric();
		$pageViews->setExpression("ga:pageviews");
		$pageViews->setAlias('pageviews');
		
		$dimensionsPagetitle = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsPagetitle->setName('ga:pageTitle');
		$dimensionsArray = array($dimensionsPagetitle);
		
		$request->setMetrics(array($pageViews));
		$request->setDimensions($dimensionsArray);
		
		// Add criteria, max results
		$request->setPageSize($params->get('ga_num_results', 24));
		
		// Add criteria, sort ordering
		$ordering = new \Google_Service_AnalyticsReporting_OrderBy();
		$ordering->setSortOrder('DESCENDING');
		$ordering->setFieldName("ga:pageviews");
		$request->setOrderBys(array($ordering));
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (!$rows) {
			return false;
		}
		
		$gadash_data = "";
		for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
			$row = $rows[ $rowIndex ];
			$rowMetrics = $row->getMetrics();
			$rowDimensions = $row->getDimensions();
			$rowValues = $rowMetrics[0]->getValues();
			$gadash_data .= "['" . addslashes ( $rowDimensions [0] ) . "'," . $rowValues[0] . "],";
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get Top systems
	 *
	 * @access private
	 * @return array
	 */
	private function getReportingTopSystems($serviceReporting, $request) {
		$params = $this->getComponentParams ();
		
		$pageViews = new \Google_Service_AnalyticsReporting_Metric();
		$pageViews->setExpression("ga:sessions");
		$pageViews->setAlias('sessions');
		
		$dimensionsBrowser = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsBrowser->setName('ga:browser');
		$dimensionsOS = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsOS->setName('ga:operatingSystem');
		$dimensionsOSVersion = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionsOSVersion->setName('ga:operatingSystemVersion');
		$dimensionsArray = array($dimensionsBrowser, $dimensionsOS, $dimensionsOSVersion);
		
		$request->setMetrics(array($pageViews));
		$request->setDimensions($dimensionsArray);
		
		// Add criteria, max results
		$request->setPageSize($params->get('ga_num_results', 24));
		
		// Add criteria, sort ordering
		$ordering = new \Google_Service_AnalyticsReporting_OrderBy();
		$ordering->setSortOrder('DESCENDING');
		$ordering->setFieldName("ga:sessions");
		$request->setOrderBys(array($ordering));
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		if (!$rows) {
			return false;
		}
		
		$gadash_data = "";
		for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
			$row = $rows[ $rowIndex ];
			$rowMetrics = $row->getMetrics();
			$rowDimensions = $row->getDimensions();
			$rowValues = $rowMetrics[0]->getValues();
			$gadash_data .= "['" . addslashes ( $rowDimensions [0] ) . "','" . addslashes ( $rowDimensions [1] ) . " " . addslashes ( $rowDimensions [2] ) . "'," . $rowValues[0] . "],";
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get sessions visits by dimension
	 *
	 * @access private
	 * @param Google_Service_AnalyticsReporting $serviceReporting
	 * @param Google_Service_AnalyticsReporting_ReportRequest $request
	 * @param string $dimension
	 *
	 * @return string
	 */
	private function getDataSessionMetric($service, $dimension, $dateRanges, $propertyID) {
		// Setup the textual data stats
		$request = new \Google_Service_AnalyticsData_RunReportRequest();
		
		// Set entity property ID
		$request->property = $propertyID;
		
		$sessions = new \Google_Service_AnalyticsData_Metric();
		$sessions->setName('sessions');
		
		$dimensionsObject = new \Google_Service_AnalyticsData_Dimension();
		$dimensionsObject->setName($dimension);
		$dimensionsArray = array($dimensionsObject);
		
		$request->setDateRanges($dateRanges);
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		$gadash_data = "";
		try {
			// Execute the ReportRequest Get for the textual stats
			$response = $service->properties->RunReport($request->property, $request);
			$rows = $response->getRows();
			
			if(!empty($rows)) {
				for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
					$row = $rows[ $rowIndex ];
					$rowValues = $row->getMetricValues();
					$rowDimensionsValues = $row->getDimensionValues();
					$gadash_data .= "['" . $rowDimensionsValues [0]->value . "'," . round ( $rowValues [0]->value, 2 ) . "],";
				}
			}
			
		} catch ( \Exception $e ) {
			if($this->getComponentParams()->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		
		if (! $gadash_data) {
			return false;
		}
		
		return $gadash_data;
	}
	
	/**
	 * Get metric by dimension
	 *
	 * @access private
	 * @param Google_Service_AnalyticsReporting $serviceReporting
	 * @param Google_Service_AnalyticsReporting_ReportRequest $request
	 * @param string $dimension
	 *
	 * @return string
	 */
	private function getDataSessionDimension($service, $dimension, $metric, $dateRanges, $propertyID) {
		// Setup the textual data stats
		$request = new \Google_Service_AnalyticsData_RunReportRequest();
		
		// Set entity property ID
		$request->property = $propertyID;
		
		$sessions = new \Google_Service_AnalyticsData_Metric();
		$sessions->setName($metric);
		
		$dimensionsObject = new \Google_Service_AnalyticsData_Dimension();
		$dimensionsObject->setName($dimension);
		$dimensionsArray = array($dimensionsObject);
		
		$request->setDateRanges($dateRanges);
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		$gadash_data = "";
		try {
			// Execute the ReportRequest Get for the textual stats
			$response = $service->properties->RunReport($request->property, $request);
			$rows = $response->getRows();
			
			if(!empty($rows)) {
				for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
					$row = $rows[ $rowIndex ];
					$rowValues = $row->getMetricValues();
					$rowDimensionsValues = $row->getDimensionValues();
					$gadash_data .= "['" . $rowDimensionsValues [0]->value . "'," . round ( $rowValues [0]->value, 2 ) . "],";
				}
			}
			
		} catch ( \Exception $e ) {
			if($this->getComponentParams()->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		
		if (! $gadash_data) {
			return false;
		}
		
		return trim($gadash_data, ',');
	}
	
	/**
	 * Get Top Pages
	 *
	 * @access private
	 * @return array
	 */
	private function getDataTopEntities($service, $dimension, $metric, $dateRanges, $propertyID) {
		$params = $this->getComponentParams ();
		
		$request = new \Google_Service_AnalyticsData_RunReportRequest();
		
		// Set entity property ID
		$request->property = $propertyID;
		
		$pageViews = new \Google_Service_AnalyticsData_Metric();
		$pageViews->setName($metric);
		
		if(is_array($dimension)) {
			$dimensionsArray = array();
			foreach ($dimension as $singleDimension) {
				$dimensionsObject = new \Google_Service_AnalyticsData_Dimension();
				$dimensionsObject->setName($singleDimension);
				$dimensionsArray[] = $dimensionsObject;
			}
		} else {
			$dimensionsObject = new \Google_Service_AnalyticsData_Dimension();
			$dimensionsObject->setName($dimension);
			$dimensionsArray = array($dimensionsObject);
		}
		
		$request->setDateRanges($dateRanges);
		$request->setMetrics(array($pageViews));
		$request->setDimensions($dimensionsArray);
		
		// Add criteria, max results
		$request->setLimit($params->get('ga_num_results', 24));
		
		// Add criteria, sort ordering
		$ordering = new \Google_Service_AnalyticsData_OrderBy();
		$orderingMetric = new \Google_Service_AnalyticsData_MetricOrderBy();
		$orderingMetric->setMetricName($metric);
		$ordering->setMetric($orderingMetric);
		$ordering->setDesc(true);
		$request->setOrderBys($ordering);
		
		$gadash_data = "";
		try {
			// Execute the ReportRequest Get for the textual stats
			$response = $service->properties->RunReport($request->property, $request);
			$rows = $response->getRows();
			
			if(!empty($rows)) {
				for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
					$row = $rows[ $rowIndex ];
					$rowValues = $row->getMetricValues();
					$rowDimensionsValues = $row->getDimensionValues();
					if(!empty($rowDimensionsValues)) {
						$gadash_data .= "[";
						foreach ($rowDimensionsValues as $rowDimensionsValue) {
							$gadash_data .= "'" . addslashes($rowDimensionsValue->value) . "',";
						}
						$gadash_data .= round ( $rowValues [0]->value, 2 ) . "],";
					}
				}
			}
			
		} catch ( \Exception $e ) {
			if($params->get('enable_debug', 0)) {
				$this->app->enqueueMessage($e->getMessage(), 'error');
			}
			return false;
		}
		
		if (! $gadash_data) {
			return false;
		}
		
		return $gadash_data;
	}

	/**
	 * Normalize Matomo endpoint URL.
	 *
	 * @access private
	 * @param string $url
	 * @return string
	 */
	private function normalizeMatomoApiUrl($url) {
		$url = trim((string)$url);
		if(!$url) {
			return '';
		}
		$url = rtrim($url, '/');
		if(substr($url, -9) !== '/index.php') {
			$url .= '/index.php';
		}
		return $url;
	}
	
	/**
	 * Perform a Matomo API request.
	 *
	 * @access private
	 * @param string $method
	 * @param array $params
	 * @return array
	 *
	 * @throws \RuntimeException
	 */
	private function callMatomoApi($method, array $params = array()) {
		$cParams = $this->getComponentParams();
		$matomoEndpoint = $this->normalizeMatomoApiUrl($cParams->get('matomo_analytics_url', ''));
		$matomoSiteId = (int)$cParams->get('matomo_analytics_idsite', 0);
		$matomoToken = trim((string)$cParams->get('matomo_token_auth', ''));
		$matomoRequestMode = strtolower(trim((string)$cParams->get('matomo_api_request_mode', 'post')));
		if(!in_array($matomoRequestMode, array('post', 'get', 'auto'))) {
			$matomoRequestMode = 'post';
		}
		
		if(!$matomoEndpoint || !$matomoSiteId || !$matomoToken) {
			throw new \RuntimeException(Text::_('COM_JREALTIME_MATOMO_MISSING_SETTINGS'));
		}
		
		$query = array_merge(
				array(
						'module' => 'API',
						'method' => $method,
						'idSite' => $matomoSiteId,
						'format' => 'JSON',
						'token_auth' => $matomoToken
				),
				$params
				);
		
		$httpTransport = $cParams->get('pagespeed_http_transport', 'curl') == 'socket' ? new Socket() : new Curl();
		$connectionAdapter = new Http($httpTransport, $cParams);
		
		$httpResponse = null;
		$attemptErrors = array();
		$allowPost = in_array($matomoRequestMode, array('post', 'auto'));
		$allowGet = in_array($matomoRequestMode, array('get', 'auto'));
		
		if($allowPost) {
			try {
				$httpResponse = $connectionAdapter->post($matomoEndpoint, $query);
			} catch (\Exception $e) {
				$attemptErrors[] = $e->getMessage();
			}
		}
		
		if((!$httpResponse || (int)$httpResponse->code >= 400 || !$httpResponse->body) && $allowGet) {
			try {
				$url = $matomoEndpoint . '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
				$httpResponse = $connectionAdapter->get($url);
			} catch (\Exception $e) {
				$attemptErrors[] = $e->getMessage();
			}
		}
		
		if(!$httpResponse || !$httpResponse->body) {
			$message = !empty($attemptErrors) ? implode(' | ', $attemptErrors) : Text::_('COM_JREALTIME_MATOMO_GENERIC_ERROR');
			throw new \RuntimeException($message);
		}
		
		if((int)$httpResponse->code >= 400) {
			throw new \RuntimeException(Text::sprintf('COM_JREALTIME_MATOMO_API_HTTP_ERROR', (int)$httpResponse->code));
		}
		
		$decodedBody = json_decode($httpResponse->body, true);
		if(!is_array($decodedBody)) {
			throw new \RuntimeException(Text::_('COM_JREALTIME_MATOMO_INVALID_JSON'));
		}
		
		if(isset($decodedBody['result']) && $decodedBody['result'] === 'error') {
			$errorMessage = isset($decodedBody['message']) ? $decodedBody['message'] : Text::_('COM_JREALTIME_MATOMO_GENERIC_ERROR');
			throw new \RuntimeException($errorMessage);
		}
		
		if(isset($decodedBody['error'])) {
			$errorMessage = is_array($decodedBody['error']) ? (isset($decodedBody['error']['message']) ? $decodedBody['error']['message'] : Text::_('COM_JREALTIME_MATOMO_GENERIC_ERROR')) : $decodedBody['error'];
			throw new \RuntimeException($errorMessage);
		}
		
		return $decodedBody;
	}
	
	/**
	 * Extract metric value from Matomo day summary.
	 *
	 * @access private
	 * @param array $rowData
	 * @param string $gaquery
	 * @return float
	 */
	private function getMatomoMetricValue(array $rowData, $gaquery) {
		switch($gaquery) {
			case 'totalUsers':
			case 'users':
				return isset($rowData['nb_uniq_visitors']) ? (float)$rowData['nb_uniq_visitors'] : 0.0;
				
			case 'screenPageViews':
			case 'pageviews':
				return isset($rowData['nb_pageviews']) ? (float)$rowData['nb_pageviews'] : 0.0;
				
			case 'engagementRate':
				$bounceRate = isset($rowData['bounce_rate']) ? (float)$rowData['bounce_rate'] : 0.0;
				return max(0, 100 - $bounceRate);
				
			case 'sessionsPerUser':
				$visits = isset($rowData['nb_visits']) ? (float)$rowData['nb_visits'] : 0.0;
				$users = isset($rowData['nb_uniq_visitors']) ? (float)$rowData['nb_uniq_visitors'] : 0.0;
				return $users > 0 ? ($visits / $users) : 0.0;
				
			case 'sessions':
			default:
				return isset($rowData['nb_visits']) ? (float)$rowData['nb_visits'] : 0.0;
		}
	}
	
	/**
	 * Build Google Charts rows from Matomo response rows.
	 *
	 * @access private
	 * @param array $rows
	 * @param string $labelField
	 * @param string $metricField
	 * @param int $limit
	 * @return string
	 */
	private function getMatomoRowsData($rows, $labelField, $metricField, $limit = 0) {
		if(!is_array($rows) || empty($rows)) {
			return '';
		}
		
		$output = '';
		$counter = 0;
		foreach($rows as $row) {
			if(!is_array($row)) {
				continue;
			}
			$label = isset($row[$labelField]) ? trim((string)$row[$labelField]) : '';
			$value = isset($row[$metricField]) ? (float)$row[$metricField] : 0.0;
			if($label === '') {
				continue;
			}
			$label = str_replace("'", "\\'", $label);
			$output .= "['" . $label . "'," . round($value, 2) . "],";
			$counter++;
			if($limit && $counter >= $limit) {
				break;
			}
		}
		
		return rtrim($output, ',');
	}
	
	/**
	 * Get Matomo systems data as browser/os/visits rows.
	 *
	 * @access private
	 * @param string $dateRange
	 * @param int $maxResults
	 * @return string
	 */
	private function getMatomoTopSystemsData($dateRange, $maxResults) {
		try {
			$browsers = $this->callMatomoApi('DevicesDetection.getBrowsers', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
			$operatingSystems = $this->callMatomoApi('DevicesDetection.getOsVersions', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
		} catch (\Exception $e) {
			return '';
		}
		
		$systemsRows = '';
		$countBrowsers = is_array($browsers) ? count($browsers) : 0;
		$countOperatingSystems = is_array($operatingSystems) ? count($operatingSystems) : 0;
		$maxRows = min(max($countBrowsers, $countOperatingSystems), $maxResults);
		
		for($rowIndex = 0; $rowIndex < $maxRows; $rowIndex++) {
			$browserLabel = isset($browsers[$rowIndex]['label']) ? $browsers[$rowIndex]['label'] : Text::_('COM_JREALTIME_SERVERSTATS_BROWSERNAME_OTHER');
			$osLabel = isset($operatingSystems[$rowIndex]['label']) ? $operatingSystems[$rowIndex]['label'] : Text::_('COM_JREALTIME_SERVERSTATS_BROWSERNAME_OTHER');
			$visits = 0;
			if(isset($browsers[$rowIndex]['nb_visits'])) {
				$visits = (float)$browsers[$rowIndex]['nb_visits'];
			} elseif(isset($operatingSystems[$rowIndex]['nb_visits'])) {
				$visits = (float)$operatingSystems[$rowIndex]['nb_visits'];
			}
			
			$browserLabel = str_replace("'", "\\'", $browserLabel);
			$osLabel = str_replace("'", "\\'", $osLabel);
			$systemsRows .= "['" . $browserLabel . "','" . $osLabel . "'," . round($visits, 2) . "],";
		}
		
		return rtrim($systemsRows, ',');
	}
	
	/**
	 * Return Google profile identifier object
	 *
	 * @access public
	 * @param string
	 * @return array
	 */
	private function getSitesProfiles($service, $client, $params) {
		try {
			$profile_switch = "";
			$profiles = $service->management_profiles->listManagementProfiles ( '~all', '~all' );
		} catch ( \Exception $e ) {
			return $e;
		}
		
		$debugBuffer = null;
		$items = $profiles->getItems ();
		if (count ( $items ) != 0) {
			foreach ( $items as &$profile ) {
				$profileid = $profile->getId ();
				$this->currentProfile = $profile;
				$currentProfileUrl = $profile->getWebsiteUrl ();
				if($params->get('enable_debug', 0)) {
					$debugBuffer .= '<li>' . $currentProfileUrl . '</li>';
				}
				if($params->get('gajs_code_use_analytics', 0)) {
					$webPropertyId = $profile->getWebPropertyId();
					if($webPropertyId == trim($params->get('gajs_code', ''))) {
						return $profileid;
					}
				}
				if ($this->purifyDomain ( $currentProfileUrl ) == $this->purifyDomain ( $params->get ( 'ga_domain', Uri::root () ) )) {
					return $profileid;
				}
			}
			// Fallback on the latest added domain to Google Analytics if no match found, with domain dumping if debug is enabled
			if($params->get('enable_debug', 0)) {
				echo Text::sprintf('COM_JREALTIME_GOOGLE_ANALYTICS_DEBUGINFO', $debugBuffer);
			}
			return $profileid;
		}
	}
	
	/**
	 * Get interface first step code
	 *
	 * @access private
	 * @param string title
	 * @param string gadash_data
	 * @param int $showevery
	 *
	 * @return string
	 */
	private function getFirstStepCode($title, $gadash_data, $showevery) {
		// Evaluate nonce csp feature
		$appNonce = $this->app->get('csp_nonce', null);
		$nonce = $appNonce ? ' nonce="' . $appNonce . '"' : '';
		$code = '<script type="text/javascript"' . $nonce . ' src="https://www.gstatic.com/charts/loader.js"></script>
		<script type="text/javascript"' . $nonce . ' src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC1xyDpvjdNwCcKmZ3StzX2oH8PfRKW_aI&callback=initJMapGoogleMap" defer></script>
      	<script type="text/javascript"' . $nonce . '>
			function initJMapGoogleMap(){};
			google.charts.load("current", {"packages": ["corechart","geochart","table"], "callback": ga_dash_callback});

			function ga_dash_callback(){
				ga_dash_drawstats();
				if(typeof ga_dash_drawmap == "function"){
					ga_dash_drawmap();
				}
				if(typeof ga_dash_drawpgd == "function"){
					ga_dash_drawpgd();
				}
				if(typeof ga_dash_drawrd == "function"){
					ga_dash_drawrd();
				}
				if(typeof ga_dash_drawsd == "function"){
					ga_dash_drawsd();
				}
				if(typeof ga_dash_drawsys == "function"){
					ga_dash_drawsys();
				}
				if(typeof ga_dash_drawtraffic == "function"){
					ga_dash_drawtraffic();
				}

				setTimeout(function() {
					window.dispatchEvent(new Event("resize"));
				}, 500);
			}

			function ga_dash_drawstats() {
		        var data = google.visualization.arrayToDataTable([' . "
					['" . Text::_ ( 'COM_JREALTIME_GOOGLE_DATE' ) . "', '" . $title . "']," . $gadash_data . "
		        ]);
							
		        var options = {
					legend: {position: 'none'},
					" . "colors:['#3366CC','#2B56AD']," . "
					pointSize: 3,
					title: '" . $title . "',
					chartArea: {width: '95%'},
					hAxis: { title: '" . Text::_ ( 'COM_JREALTIME_GOOGLE_DATE' ) . "',  titleTextStyle: {color: 'black'}, showTextEvery: " . $showevery . "},
					vAxis: { textPosition: 'none', minValue: 0}
				};
							
				var chart = new google.visualization.AreaChart(document.getElementById('gadash_div'));
				chart.draw(data, options);
			}";
		
		return $code;
	}
	
	/**
	 * Get interface visits by country code
	 *
	 * @access private
	 * @param string $getVisitsByCountry
	 *
	 * @return string
	 */
	private function getVisitsByCountryCode($getVisitsByCountry) {
		$code = '
		function ga_dash_drawmap() {
			var data = google.visualization.arrayToDataTable([' . "
			  ['Country', 'Visits']," . $getVisitsByCountry . "
			]);
			  		
			var options = {
				colors: ['white', '" . "blue" . "']
			};
						
			var chart = new google.visualization.GeoChart(document.getElementById('ga_dash_mapdata'));
			chart.draw(data, options);
		}";
		
		return $code;
	}
	
	/**
	 * Get interface traffic code
	 *
	 * @access private
	 * @param string $getTrafficSources
	 * @param string $getNewReturnVisitors
	 *
	 * @return string
	 */
	private function getTrafficCode($getTrafficSources, $getNewReturnVisitors) {
		$code = '
		function ga_dash_drawtraffic() {
			var data = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_GOOGLE_SOURCE') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getTrafficSources . '
			]);
			  		
			var datanvr = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_TYPE') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getNewReturnVisitors . "
			]);
			  		
			var chart = new google.visualization.PieChart(document.getElementById('ga_dash_trafficdata'));
			chart.draw(data, {
					tooltip: {showColorCode: true},
					is3D: true,
					height: 400,
					tooltipText: 'percentage',
					legend: {position: 'right', textStyle: {color: 'blue', fontSize: 16}},
					title: '" . Text::_('COM_JREALTIME_GOOGLE_TRAFFIC_SOURCES') . "'
			});
							
			var gadash = new google.visualization.PieChart(document.getElementById('ga_dash_nvrdata'));
			gadash.draw(datanvr,  {
					tooltip: {showColorCode: true},
					is3D: true,
					height: 400,
					tooltipText: 'percentage',
					legend: {position: 'right', textStyle: {color: 'blue', fontSize: 16}},
					title: '" . Text::_('COM_JREALTIME_GOOGLE_NEW_RETURNING') . "'
			});
							
			setTimeout(function(){
				if(document.querySelector('#ga_dash_trafficdata svg ~ div')) {
					document.querySelector('#ga_dash_trafficdata svg ~ div').classList.add('table', 'table-striped');
					document.querySelector('#ga_dash_nvrdata svg ~ div').classList.add('table', 'table-striped');
				}
			}, 500);
		}";
		
		return $code;
	}
	
	/**
	 * Get interface pages code
	 *
	 * @access private
	 * @param string $getTopPages
	 *
	 * @return string
	 */
	private function getTopPagesCode($getTopPages) {
		$code = '
		function ga_dash_drawpgd() {
			var data = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_GOOGLE_TOPPAGES') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getTopPages . "
			]);
			  		
			var options = {
					page: 'enable',
					pageSize: 6,
					width: '100%'
			};
			  		
			var chart = new google.visualization.Table(document.getElementById('ga_dash_pgddata'));
			chart.draw(data, options);
		}";
		return $code;
	}
	
	/**
	 * Get interface referrers code
	 *
	 * @access private
	 * @param string $getTopReferrers
	 *
	 * @return string
	 */
	private function getTopReferrers($getTopReferrers) {
		$code = '
		function ga_dash_drawrd() {
			var datar = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_GOOGLE_TOPREFERRERS') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getTopReferrers . "
			]);
			  		
			var options = {
					page: 'enable',
					pageSize: 6,
					width: '100%'
			};
			  		
			var chart = new google.visualization.Table(document.getElementById('ga_dash_rdata'));
			chart.draw(datar, options);
		}";
		return $code;
	}
	
	/**
	 * Get interface searches code
	 *
	 * @access private
	 * @param string $getTopSearches
	 *
	 * @return string
	 */
	private function getTopSearches($getTopSearches) {
		$code = '
		function ga_dash_drawsd() {
			var datas = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_GOOGLE_TOPSEARCHES') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getTopSearches . "
			]);
			  		
			var options = {
					page: 'enable',
					pageSize: 6,
					width: '100%'
			};
			  		
			var chart = new google.visualization.Table(document.getElementById('ga_dash_sdata'));
			chart.draw(datas, options);
		}";
		return $code;
	}
	
	/**
	 * Get interface searches code
	 *
	 * @access private
	 * @param string $getTopSearches
	 *
	 * @return string
	 */
	private function getTopSystems($getTopSystems) {
		$code = '
		function ga_dash_drawsys() {
			var datas = google.visualization.arrayToDataTable([' . "
			  ['" . Text::_('COM_JREALTIME_GOOGLE_TOPBROWSER') . "', '" . Text::_('COM_JREALTIME_GOOGLE_TOPSYSTEM') . "', '" . Text::_('COM_JREALTIME_GOOGLE_VISITS') . "']," . $getTopSystems . "
			]);
			  		
			var options = {
					page: 'enable',
					pageSize: 6,
					width: '100%'
			};
			  		
			var chart = new google.visualization.Table(document.getElementById('ga_dash_sysdata'));
			chart.draw(datas, options);
		}";
		return $code;
	}
	
	/**
	 * Get interface resize function
	 *
	 * @access private
	 * @param string $getTopSearches
	 *
	 * @return string
	 */
	private function getWindowResize() {
		$code = "window.addEventListener('resize', function(){
			if(typeof ga_dash_drawstats == 'function'){
				ga_dash_drawstats();
			}
			if(typeof ga_dash_drawmap == 'function'){
				ga_dash_drawmap();
			}
			if(typeof ga_dash_drawpgd == 'function'){
				ga_dash_drawpgd();
			}
			if(typeof ga_dash_drawrd == 'function'){
				ga_dash_drawrd();
			}
			if(typeof ga_dash_drawsd == 'function'){
				ga_dash_drawsd();
			}
			if(typeof ga_dash_drawsys == 'function'){
				ga_dash_drawsys();
			}
			if(typeof ga_dash_drawtraffic == 'function'){
				ga_dash_drawtraffic();
			}
		});

		if(document.querySelector('#menu-collapse')) {
			document.querySelector('#menu-collapse').addEventListener('click', function(){
				setTimeout(function() {
					window.dispatchEvent(new Event('resize'));
				}, 500);
			});
		}

		setTimeout(function() {
			window.dispatchEvent(new Event('resize'));
		}, 500);
		</script>";

		return $code;
	}
	
	/**
	 * Get interface period buttons
	 *
	 * @access private
	 * @param string $gaperiod
	 *
	 * @return string
	 */
	private function getPeriodButtons($gaperiod) {
		$code = '<div class="btn-toolbar mb-2">
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "today" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'today\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_TODAY' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "yesterday" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'yesterday\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_YESTERDAY' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last7days" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last7days\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST7DAYS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last14days" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last14days\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST14DAYS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last30days" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last30days\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST30DAYS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last3months" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last3months\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST3MONTHS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last6months" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last6months\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST6MONTHS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaperiod == "last12months" ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaperiod\').value=\'last12months\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_LAST12MONTHS' ) . '</button></div>
				</div>';
		
		return $code;
	}
	
	/**
	 * Get interface metric buttons
	 *
	 * @access private
	 * @param string $gaperiod
	 *
	 * @return string
	 */
	private function getMetricButtons($gaquery, $metric1, $metric2, $metric3, $metric4, $metric5) {
		$code = '<div class="btn-toolbar">
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaquery == $metric1 ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaquery\').value=\'' . $metric1 . '\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_VISITORS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaquery == $metric2 ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaquery\').value=\'' . $metric2 . '\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_PAGEVIEWS' ) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaquery == $metric3 ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaquery\').value=\'' . $metric3 . '\'">' . ($metric3 == 'bounceRate' ? Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_BOUNCERATE' ) : Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_ENGAGEMENTRATE' )) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaquery == $metric4 ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaquery\').value=\'' . $metric4 . '\'">' . ($metric4 == 'organicSearches' ? Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_ORGANICSEARCHES' ) : Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_SESSIONSPERUSER' )) . '</button></div>
					<div class="btn-wrapper"><button class="btn btn-info' . ($gaquery == $metric5 ? ' active' : '') . '" role="button" onclick="document.getElementById(\'gaquery\').value=\'' . $metric5 . '\'">' . Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_VISITS' ) . '</button></div>
				</div>';
		
		return $code;
	}
	
	/**
	 * Get interface for main multireports
	 *
	 * @access private
	 * @param string $gaperiod
	 *
	 * @return string
	 */
	private function getMultiReports($text1, $text2, $text3, $text4, $text5, $text6, $customIcon = 'fa-search') {
		$JoomlaCMSLanguageText = '\Joomla\CMS\Language\Text';
		$multiReports = <<<MULTIREPORTS
							<div class="card card-info card-group-google" id="jrealtime_googlegeo_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_geo">
									<h4><span class="fas fa-image" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_ ($text1)}</h4>
								</div>
								<div id="jrealtime_googlestats_geo" class="card-body card-block accordion-chart">
									<div id="ga_dash_mapdata"></div>
								</div>
							</div>
							
							<div class="card card-info card-group-google" id="jrealtime_googletraffic_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_traffic">
									<h4><span class="fas fa-sort" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_ ($text2)}</h4>
								</div>
								<div id="jrealtime_googlestats_traffic" class="card-body card-block accordion-chart">
									<div id="ga_dash_trafficdata"></div><div id="ga_dash_nvrdata"></div>
								</div>
							</div>
							
							<div class="card card-info card-group-google" id="jrealtime_googlereferrer_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_referrers">
									<h4><span class="fas fa-caret-square-down" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_ ($text3)}</h4>
								</div>
								<div id="jrealtime_googlestats_referrers" class="card-body card-block accordion-chart">
									<div id="ga_dash_rdata"></div>
								</div>
							</div>
							
							<div class="card card-info card-group-google" id="jrealtime_googlesearches_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_searches">
									<h4><span class="fas {$customIcon}" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_ ($text4)}</h4>
								</div>
								<div id="jrealtime_googlestats_searches" class="card-body card-block accordion-chart">
									<div id="ga_dash_sdata"></div>
								</div>
							</div>
							
							<div class="card card-info card-group-google" id="jrealtime_googlesystems_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_systems">
									<h4><span class="fas fa-desktop" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_($text5)}</h4>
								</div>
								<div id="jrealtime_googlestats_systems" class="card-body card-block accordion-chart">
									<div id="ga_dash_sysdata"></div>
								</div>
							</div>
							
							<div class="card card-info card-group-google" id="jrealtime_googlepages_accordion">
								<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_pages">
									<h4><span class="fas fa-file" aria-hidden="true"></span> {$JoomlaCMSLanguageText::_ ($text6)}</h4>
								</div>
								<div id="jrealtime_googlestats_pages" class="card-body card-block accordion-chart">
									<div id="ga_dash_pgddata"></div>
								</div>
							</div>
MULTIREPORTS;
		return $multiReports;
	}
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataAnalytics() {
		$params = $this->getComponentParams ();
		
		// Perform the authentication management before going on
		$authenticationData = $this->authentication ( $params );
		if($authenticationData) {
			return $authenticationData;
		}
		
		// New Service instance for the API, Google_Service_Analytics
		$service = new \Google_Service_Analytics ( $this->client );
		
		$projectId = $this->getSitesProfiles ( $service, $this->client, $params );
		
		if ( $projectId instanceof \Exception ) {
			$this->deleteToken();
			$this->app->enqueueMessage ( $projectId->getMessage (), 'warning' );
			return '<a class="btn btn-primary" href="index.php?option=com_jrealtimeanalytics&amp;task=google.display">' . Text::_ ( 'COM_JREALTIME_GOBACK' ) . '</a>';
		}
		
		if ($this->app->getInput()->get('gaquery')) {
			$gaquery = $this->app->getInput()->get('gaquery');
		} else {
			$gaquery = "sessions";
		}
		
		// Evaluate if the metric exists in this API, there could be an API change with incompatible metric so fallback in this case
		if(!in_array($gaquery, array('users', 'pageviews', 'bounceRate', 'organicSearches', 'sessions'))) {
			$gaquery = "sessions";
		}
		
		if ($this->app->getInput()->get('gaperiod')) {
			$gaperiod = $this->app->getInput()->get('gaperiod');
		} else {
			$gaperiod = "last30days";
		}
		
		extract($this->getPeriod ( $gaperiod ));
		$title = $this->getTitle ( $gaquery );
		
		$metrics = 'ga:' . $gaquery;
		$dimensions = 'ga:year,ga:month,ga:day';
		
		if ($gaperiod == "today" or $gaperiod == "yesterday") {
			$dimensions = 'ga:hour';
		} else {
			$dimensions = 'ga:year,ga:month,ga:day';
		}
		
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions
			) );
		} catch ( \Exception $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), 'error' );
			return null;
		}
		$gadash_data = "";
		for($i = 0; $i < $data ['totalResults']; $i ++) {
			if ($gaperiod == "today" or $gaperiod == "yesterday") {
				$gadash_data .= "['" . $data ['rows'] [$i] [0] . ":00'," . round ( $data ['rows'] [$i] [1], 2 ) . "],";
			} else {
				$gadash_data .= "['" . $data ['rows'] [$i] [0] . "-" . $data ['rows'] [$i] [1] . "-" . $data ['rows'] [$i] [2] . "'," . round ( $data ['rows'] [$i] [3], 2 ) . "],";
			}
		}
		// Avoid errors in the drawing phase of the visits map
		if(!$gadash_data) {
			$gadash_data = "['" . date('Y-m-d') . "',0]";
		}
		
		$metrics = 'ga:sessions,ga:users,ga:pageviews,ga:bounceRate,ga:organicSearches,ga:sessionDuration';
		$dimensions = 'ga:year';
		try {
			$data = $service->data_ga->get ( 'ga:' . $projectId, $from, $to, $metrics, array (
					'dimensions' => $dimensions
			) );
			
			// Normalize the Google Analytics data rows if multiple ones are found, merge into a single one
			if(!empty($data['rows']) && count($data['rows']) > 1) {
				$newDataArray = array();
				$newDataArray[0][1] = 0;
				$newDataArray[0][2] = 0;
				$newDataArray[0][3] = 0;
				$newDataArray[0][4] = 0;
				$newDataArray[0][5] = 0;
				$numElems = count($data['rows']);
				for($indexRow = 0; $indexRow <= ($numElems - 1); $indexRow++) {
					$newDataArray[0][1] += $data['rows'][$indexRow][1];
					$newDataArray[0][2] += $data['rows'][$indexRow][2];
					$newDataArray[0][3] += $data['rows'][$indexRow][3];
					$newDataArray[0][4] += $data['rows'][$indexRow][4];
					$newDataArray[0][5] += $data['rows'][$indexRow][5];
				}
				// Normalize bounce rate media
				$newDataArray[0][4] = $newDataArray[0][4] / $numElems;
				// Set single normalized row
				$data->setRows($newDataArray);
			}
			// If no data, e.g. an empty property, initialize rows to zero to avoid warnings
			if(empty($data['rows'])) {
				$newDataArray = array();
				$newDataArray[0][1] = 0;
				$newDataArray[0][2] = 0;
				$newDataArray[0][3] = 0;
				$newDataArray[0][4] = 0;
				$newDataArray[0][5] = 0;
				$data->setRows($newDataArray);
			}
		} catch ( \Exception $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), 'error' );
			return null;
		}
		
		$code = $this->getFirstStepCode ( $title, $gadash_data, $showevery );
		
		$getVisitsByCountry = $this->getAnalyticsVisitsByCountry ( $service, $projectId, $from, $to, $params );
		if ($getVisitsByCountry) {
			$code .= $this->getVisitsByCountryCode ( $getVisitsByCountry );
		}
		
		$getTrafficSources = $this->getAnalyticsTrafficSources ( $service, $projectId, $from, $to, $params );
		$getNewReturnVisitors = $this->getAnalyticsNewReturnVisitors ( $service, $projectId, $from, $to, $params );
		if ($getTrafficSources && $getNewReturnVisitors) {
			$code .= $this->getTrafficCode ( $getTrafficSources, $getNewReturnVisitors );
		}
		
		$getTopPages = $this->getAnalyticsTopPages ( $service, $projectId, $from, $to, $params );
		if ($getTopPages) {
			$code .= $this->getTopPagesCode ( $getTopPages );
		}
		
		$getTopReferrers = $this->getAnalyticsTopReferrers ( $service, $projectId, $from, $to, $params );
		if ($getTopReferrers) {
			$code .= $this->getTopReferrers ( $getTopReferrers );
		}
		
		$getTopSystems = $this->getAnalyticsTopSystems ( $service, $projectId, $from, $to, $params );
		if ($getTopSystems) {
			$code .= $this->getTopSystems ( $getTopSystems );
		}
		
		$getTopSearches = $this->getAnalyticsTopSearches ( $service, $projectId, $from, $to, $params );
		if ($getTopSearches) {
			$code .= $this->getTopSearches ( $getTopSearches );
		}
		
		$code .= $this->getWindowResize() .
		($this->currentProfile->getWebsiteUrl() ? "<span class='badge bg-primary mb-2'>" . $this->currentProfile->getWebsiteUrl() . "</span>" : null) .
		($this->hasOwnCredentials ? null : "<span data-bs-content='" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET_DESC') . "' class='badge bg-warning hasPopover google pull-right'>" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET') . "</span>") .
		'<div id="ga-dash">' .
		$this->getPeriodButtons($gaperiod) .
		'<div class="card card-info card-group-google" id="jrealtime_googlegraph_accordion">
			<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_graph">
				<h4><span class="fas fa-chart-area" aria-hidden="true"></span> ' . Text::_ ('COM_JREALTIME_GOOGLE_STATS' ) . '</h4>
			</div>
			<div id="jrealtime_googlestats_graph" class="card-body card-block accordion-chart">'.
			$this->getMetricButtons($gaquery, 'users', 'pageviews', 'bounceRate', 'organicSearches', 'sessions') .
			'<div id="gadash_div" style="height:350px;"></div>
				<table class="gatable" cellpadding="4" width="100%" align="center">
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $data ['rows'] [0] [1] . '</td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITORS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $data ['rows'] [0] [2] . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGE_VIEWS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $data ['rows'] [0] [3] . '</a></td>
					</tr>
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_BOUNCE_RATE' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . round ( $data ['rows'] [0] [4], 2 ) . '%</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_ORGANIC_SEARCHES' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $data ['rows'] [0] [5] . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGES_VISIT' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (($data ['rows'] [0] [1]) ? round ( $data ['rows'] [0] [3] / $data ['rows'] [0] [1], 2 ) : '0') . '</a></td>
					</tr>
				</table>
			</div>
		</div>';
			
			$multiReports = $this->getMultiReports ('COM_JREALTIME_GOOGLE_MAP', 'COM_JREALTIME_GOOGLE_TRAFFIC', 'COM_JREALTIME_GOOGLE_REFERRERS', 'COM_JREALTIME_GOOGLE_SEARCHES', 'COM_JREALTIME_GOOGLE_SYSTEMS', 'COM_JREALTIME_GOOGLE_PAGES');
			$code .= $multiReports;
			$code .= '</div>';
			
			return $code;
	}
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataReporting() {
		$params = $this->getComponentParams ();
		
		// Perform the authentication management before going on
		$authenticationData = $this->authentication ( $params );
		if($authenticationData) {
			return $authenticationData;
		}
		
		// New Service instance for the API, Google_Service_Analytics
		$service = new \Google_Service_Analytics ( $this->client );
		$serviceReporting = new \Google_Service_AnalyticsReporting( $this->client );
		
		$projectId = $this->getSitesProfiles ( $service, $this->client, $params );
		
		if ( $projectId instanceof \Exception ) {
			$this->deleteToken();
			$this->app->enqueueMessage ( $projectId->getMessage (), 'warning' );
			return '<a class="btn btn-primary" href="index.php?option=com_jrealtimeanalytics&amp;task=google.display">' . Text::_ ( 'COM_JREALTIME_GOBACK' ) . '</a>';
		}
		
		if ($this->app->getInput()->get('gaquery')) {
			$gaquery = $this->app->getInput()->get('gaquery');
		} else {
			$gaquery = "sessions";
		}
		
		// Evaluate if the metric exists in this API, there could be an API change with incompatible metric so fallback in this case
		if(!in_array($gaquery, array('users', 'pageviews', 'bounceRate', 'organicSearches', 'sessions'))) {
			$gaquery = "sessions";
		}
		
		if ($this->app->getInput()->get('gaperiod')) {
			$gaperiod = $this->app->getInput()->get('gaperiod');
		} else {
			$gaperiod = "last30days";
		}
		
		extract($this->getPeriod ( $gaperiod ));
		$title = $this->getTitle ( $gaquery );
		
		// Create the DateRange object
		$dateRange = new \Google_Service_AnalyticsReporting_DateRange();
		$dateRange->setStartDate($from);
		$dateRange->setEndDate($to);
		
		// Create the Metric object
		$sessions = new \Google_Service_AnalyticsReporting_Metric();
		$sessions->setExpression("ga:" . $gaquery);
		$sessions->setAlias($gaquery);
		
		//Create the Dimensions object.
		$dimensionsArray = array();
		if ($gaperiod == "today" or $gaperiod == "yesterday") {
			$dimensionsHour = new \Google_Service_AnalyticsReporting_Dimension();
			$dimensionsHour->setName("ga:hour");
			$dimensionsArray = array($dimensionsHour);
		} else {
			$dimensionsYear = new \Google_Service_AnalyticsReporting_Dimension();
			$dimensionsYear->setName("ga:year");
			$dimensionsMonth = new \Google_Service_AnalyticsReporting_Dimension();
			$dimensionsMonth->setName("ga:month");
			$dimensionsDay = new \Google_Service_AnalyticsReporting_Dimension();
			$dimensionsDay->setName("ga:day");
			$dimensionsArray = array($dimensionsYear, $dimensionsMonth, $dimensionsDay);
		}
		
		// Create the ReportRequest object.
		$request = new \Google_Service_AnalyticsReporting_ReportRequest();
		$request->setViewId($projectId);
		$request->setDateRanges($dateRange);
		$request->setMetrics(array($sessions));
		$request->setDimensions($dimensionsArray);
		
		try {
			// Execute the ReportRequest Get for the main graph data
			$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
			$getRequest->setReportRequests( array( $request) );
			$response = $serviceReporting->reports->batchGet( $getRequest );
			$rows = $response->getReports()[0]->getData()->getRows();
		} catch ( \Exception $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), 'error' );
			return null;
		}
		
		if(!empty($rows)) {
			$gadash_data = null;
			for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
				$row = $rows[ $rowIndex ];
				$rowMetrics = $row->getMetrics();
				$rowDimensions = $row->getDimensions();
				$rowValues = $rowMetrics[0]->getValues();
				if ($gaperiod == "today" or $gaperiod == "yesterday") {
					$gadash_data .= "['" . $rowDimensions [0] . ":00'," . round ( $rowValues [0], 2 ) . "],";
				} else {
					$gadash_data .= "['" . $rowDimensions [0] . "-" . $rowDimensions [1] . "-" . $rowDimensions [2] . "'," . round ( $rowValues [0], 2 ) . "],";
				}
			}
		} else {
			$gadash_data = "[0,0]";
		}
		
		// Setup the textual data stats
		$sessions = new \Google_Service_AnalyticsReporting_Metric();
		$sessions->setExpression("ga:sessions");
		$sessions->setAlias('sessions');
		
		$users = new \Google_Service_AnalyticsReporting_Metric();
		$users->setExpression("ga:users");
		$users->setAlias('users');
		
		$pageViews = new \Google_Service_AnalyticsReporting_Metric();
		$pageViews->setExpression("ga:pageviews");
		$pageViews->setAlias('pageviews');
		
		$bounceRate = new \Google_Service_AnalyticsReporting_Metric();
		$bounceRate->setExpression("ga:bounceRate");
		$bounceRate->setAlias('bounceRate');
		
		$organicSearches = new \Google_Service_AnalyticsReporting_Metric();
		$organicSearches->setExpression("ga:organicSearches");
		$organicSearches->setAlias('organicSearches');
		
		$sessionDuration = new \Google_Service_AnalyticsReporting_Metric();
		$sessionDuration->setExpression("ga:sessionDuration");
		$sessionDuration->setAlias('sessionDuration');
		
		$dimensionYear = new \Google_Service_AnalyticsReporting_Dimension();
		$dimensionYear->setName("ga:year");
		$dimensionYear = array($dimensionYear);
		
		$request->setMetrics(array($sessions, $users, $pageViews, $bounceRate, $organicSearches, $sessionDuration));
		$request->setDimensions($dimensionYear);
		
		// Execute the ReportRequest Get for the textual stats
		$getRequest = new \Google_Service_AnalyticsReporting_GetReportsRequest();
		$getRequest->setReportRequests( array( $request) );
		$response = $serviceReporting->reports->batchGet($getRequest);
		$rows = $response->getReports()[0]->getData()->getRows();
		
		$textualData = array(null,null,null,null,null,null);
		if(!empty($rows)) {
			$row = $rows[0];
			$rowMetrics = $row->getMetrics();
			$textualData = $rowMetrics[0]->getValues();
		}
		
		$code = $this->getFirstStepCode ( $title, $gadash_data, $showevery );
		
		$getVisitsByCountry = $this->getReportingSessionMetric ( $serviceReporting, $request, 'ga:country' );
		if ($getVisitsByCountry) {
			$code .= $this->getVisitsByCountryCode ( $getVisitsByCountry );
		}
		
		$getTrafficSources = $this->getReportingSessionMetric ( $serviceReporting, $request, 'ga:medium' );
		$getNewReturnVisitors = $this->getReportingSessionMetric ( $serviceReporting, $request, 'ga:userType' );
		if ($getTrafficSources && $getNewReturnVisitors) {
			$getTrafficSources = StringHelper::str_ireplace('(none)', 'direct', $getTrafficSources);
			$code .= $this->getTrafficCode ( $getTrafficSources, $getNewReturnVisitors );
		}
		
		$getTopPages = $this->getReportingTopPages ( $serviceReporting, $request );
		if ($getTopPages) {
			$code .= $this->getTopPagesCode ( $getTopPages );
		}
		
		$getTopReferrers = $this->getReportingTopReferrers ( $serviceReporting, $request );
		if ($getTopReferrers) {
			$code .= $this->getTopReferrers ( $getTopReferrers );
		}
		
		$getTopSystems = $this->getReportingTopSystems ( $serviceReporting, $request );
		if ($getTopSystems) {
			$code .= $this->getTopSystems ( $getTopSystems );
		}
		
		$getTopSearches = $this->getReportingTopSearches ( $serviceReporting, $request );
		if ($getTopSearches) {
			$code .= $this->getTopSearches ( $getTopSearches );
		}
		
		$code .= $this->getWindowResize() .
		($this->currentProfile->getWebsiteUrl() ? "<span class='badge bg-primary mb-2'>" . $this->currentProfile->getWebsiteUrl() . "</span>" : null) .
		($this->hasOwnCredentials ? null : "<span data-bs-content='" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET_DESC') . "' class='badge bg-warning hasPopover google pull-right'>" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET') . "</span>") .
		'<div id="ga-dash">' .
		$this->getPeriodButtons($gaperiod) .
		'<div class="card card-info card-group-google" id="jrealtime_googlegraph_accordion">
			<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_graph">
				<h4><span class="fas fa-chart-area" aria-hidden="true"></span> ' . Text::_ ('COM_JREALTIME_GOOGLE_STATS' ) . '</h4>
			</div>
			<div id="jrealtime_googlestats_graph" class="card-body card-block accordion-chart" >' .
			$this->getMetricButtons($gaquery, 'users', 'pageviews', 'bounceRate', 'organicSearches', 'sessions') .
			'<div id="gadash_div" style="height:350px;"></div>
				<table class="gatable" cellpadding="4" width="100%" align="center">
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[0] . '</td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITORS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[1] . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGE_VIEWS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[2] . '</a></td>
					</tr>
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_BOUNCE_RATE' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . round ( $textualData[3], 2 ) . '%</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_ORGANIC_SEARCHES' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[4] . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGES_VISIT' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (($textualData[0]) ? round ( $textualData[2] / $textualData[0], 2 ) : '0') . '</a></td>
					</tr>
				</table>
			</div>
		</div>';
			
			$multiReports = $this->getMultiReports ('COM_JREALTIME_GOOGLE_MAP', 'COM_JREALTIME_GOOGLE_TRAFFIC', 'COM_JREALTIME_GOOGLE_REFERRERS', 'COM_JREALTIME_GOOGLE_SEARCHES', 'COM_JREALTIME_GOOGLE_SYSTEMS', 'COM_JREALTIME_GOOGLE_PAGES');
			$code .= $multiReports;
			$code .= '</div>';
			
			return $code;
	}
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return mixed Returns a data string if success or boolean if exceptions are trigged
	 */
	public function getDataData() {
		$params = $this->getComponentParams ();
		
		// Perform the authentication management before going on
		$authenticationData = $this->authentication ( $params );
		if($authenticationData) {
			return $authenticationData;
		}
		
		// Using a default constructor instructs the client to use the credentials
		// specified in GOOGLE_APPLICATION_CREDENTIALS environment variable.
		$service = new \Google_Service_AnalyticsData($this->client);
		
		if ($this->app->getInput()->get('gaquery')) {
			$gaquery = $this->app->getInput()->get('gaquery');
		} else {
			$gaquery = "sessions";
		}
		
		// Evaluate if the metric exists in this API, there could be an API change with incompatible metric so fallback in this case
		if(!in_array($gaquery, array('totalUsers', 'screenPageViews', 'engagementRate', 'sessionsPerUser', 'sessions'))) {
			$gaquery = "sessions";
		}
		
		if ($this->app->getInput()->get('gaperiod')) {
			$gaperiod = $this->app->getInput()->get('gaperiod');
		} else {
			$gaperiod = "last30days";
		}
		
		extract($this->getPeriod ( $gaperiod, true ));
		$title = $this->getTitle ( $gaquery );
		
		// Create the DateRange object
		$dateRanges = new \Google_Service_AnalyticsData_DateRange();
		$dateRanges->setStartDate($from);
		$dateRanges->setEndDate($to);
		
		// Create the Metric object
		$metrics = new \Google_Service_AnalyticsData_Metric();
		$metrics->setName($gaquery);
		
		//Create the Dimensions object.
		$dimensionsArray = array();
		// Set dimensions
		$dimensionsYear = new \Google_Service_AnalyticsData_Dimension();
		$dimensionsYear->setName("year");
		$dimensionsMonth = new \Google_Service_AnalyticsData_Dimension();
		$dimensionsMonth->setName("month");
		$dimensionsDay = new \Google_Service_AnalyticsData_Dimension();
		$dimensionsDay->setName("day");
		$dimensionsArray = array($dimensionsYear, $dimensionsMonth, $dimensionsDay);
		
		// Create the ReportRequest object.
		$request = new \Google_Service_AnalyticsData_RunReportRequest();
		
		// Add criteria, sort ordering
		$orderingYear = new \Google_Service_AnalyticsData_OrderBy();
		$orderingDimensionYear = new \Google_Service_AnalyticsData_DimensionOrderBy();
		$orderingDimensionYear->setDimensionName("year");
		$orderingYear->setDimension($orderingDimensionYear);
		$orderingYear->setDesc(false);
		
		$orderingMonth = new \Google_Service_AnalyticsData_OrderBy();
		$orderingDimensionMonth = new \Google_Service_AnalyticsData_DimensionOrderBy();
		$orderingDimensionMonth->setDimensionName("month");
		$orderingMonth->setDimension($orderingDimensionMonth);
		$orderingMonth->setDesc(false);
		
		$orderingDay = new \Google_Service_AnalyticsData_OrderBy();
		$orderingDimensionDay = new \Google_Service_AnalyticsData_DimensionOrderBy();
		$orderingDimensionDay->setDimensionName("day");
		$orderingDay->setDimension($orderingDimensionDay);
		$orderingDay->setDesc(false);
		
		$request->setOrderBys(array($orderingYear, $orderingMonth, $orderingDay));
		
		// Set entity property ID
		$propertyID = $params->get('ga_property_id');
		if(!$propertyID) {
			$this->app->enqueueMessage ( Text::_('COM_JREALTIME_GOOGLE_MISSING_PROPERTY_ID'), 'error' );
			return null;
		}
		
		$request->property = 'properties/' .  $propertyID;
		
		$request->setDateRanges($dateRanges);
		$request->setMetrics($metrics);
		$request->setDimensions($dimensionsArray);
		
		// Execute the ReportRequest Get for the main graph data
		try {
			$response = $service->properties->RunReport($request->property, $request);
			$rows = $response->getRows();
		} catch ( \Exception $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), 'error' );
			return null;
		}
		
		if(!empty($rows)) {
			$gadash_data = null;
			for($rowIndex = 0; $rowIndex < count($rows); $rowIndex ++) {
				$row = $rows[ $rowIndex ];
				$rowValues = $row->getMetricValues();
				$rowDimensionsValues = $row->getDimensionValues();
				
				$gadash_data .= "['" . $rowDimensionsValues [0]->value . "-" . $rowDimensionsValues [1]->value . "-" . $rowDimensionsValues [2]->value . "'," . round ( $rowValues [0]->value, 2 ) . "],";
			}
		} else {
			$gadash_data = "[0,0]";
		}
		
		// Setup the textual data stats
		$request = new \Google_Service_AnalyticsData_RunReportRequest();
		$sessions = new \Google_Service_AnalyticsData_Metric();
		$sessions->setName('sessions');
		
		$users = new \Google_Service_AnalyticsData_Metric();
		$users->setName('totalUsers');
		
		$pageViews = new \Google_Service_AnalyticsData_Metric();
		$pageViews->setName('screenPageViews');
		
		$engagementRate = new \Google_Service_AnalyticsData_Metric();
		$engagementRate->setName('engagementRate');
		
		$sessionsPerUser = new \Google_Service_AnalyticsData_Metric();
		$sessionsPerUser->setName('sessionsPerUser');
		
		$sessionDuration = new \Google_Service_AnalyticsData_Metric();
		$sessionDuration->setName('userEngagementDuration');
		
		$dimensionYear = new \Google_Service_AnalyticsData_Dimension();
		$dimensionYear->setName("year");
		$dimensionsArray = array($dimensionYear);
		
		$request->setDateRanges($dateRanges);
		$request->setMetrics(array($sessions, $users, $pageViews, $engagementRate, $sessionsPerUser, $sessionDuration));
		$request->setDimensions($dimensionsArray);
		
		// Set entity property ID
		$request->property = 'properties/' .  $propertyID;
		
		// Execute the ReportRequest Get for the textual stats
		$response = $service->properties->RunReport($request->property, $request);
		$rows = $response->getRows();
		
		$textualData = array(null,null,null,null,null,null);
		if(!empty($rows)) {
			$row = $rows[0];
			$textualData = $row->getMetricValues();
		}
		
		$code = $this->getFirstStepCode ( $title, $gadash_data, $showevery );
		
		$getVisitsByCountry = $this->getDataSessionMetric ( $service, 'country', $dateRanges, $request->property );
		if ($getVisitsByCountry) {
			$code .= $this->getVisitsByCountryCode ( $getVisitsByCountry );
		}
		
		$getTrafficSources = $this->getDataSessionMetric ( $service, 'sessionDefaultChannelGrouping', $dateRanges, $request->property );
		$getNewVisitors = str_ireplace(array('[', ']'), '', explode(',', $this->getDataSessionDimension ( $service, 'year', 'newUsers', $dateRanges, $request->property )));
		$getNewVisitors[0] = Text::_('COM_JREALTIME_GOOGLE_NEW_VISITORS');
		$getTotalVisitors = str_ireplace(array('[', ']'), '', explode(',', $this->getDataSessionDimension ( $service, 'year', 'totalUsers', $dateRanges, $request->property )));
		$getTotalVisitors[0] = Text::_('COM_JREALTIME_GOOGLE_RETURNING_VISITORS');
		$getTotalVisitors[1] = isset($getNewVisitors[1]) ? abs($getTotalVisitors[1] - $getNewVisitors[1]) : (isset($getTotalVisitors[1]) ? $getTotalVisitors[1] : 0);
		$newVisitorsDashData = "['" . $getNewVisitors[0] . "'," . (isset($getNewVisitors[1]) ? round ( $getNewVisitors[1], 2 ) : 0) . "],";
		$returningVisitorsDashData = "['" . $getTotalVisitors[0] . "'," . round ( $getTotalVisitors[1], 2 ) . "]";
		$getNewReturnVisitors = $newVisitorsDashData . $returningVisitorsDashData;
		
		if ($getTrafficSources && $getNewReturnVisitors) {
			$code .= $this->getTrafficCode ( $getTrafficSources, $getNewReturnVisitors );
		}
		
		$getTopPages = $this->getDataTopEntities ( $service, 'pageTitle', 'screenPageViews', $dateRanges, $request->property );
		if ($getTopPages) {
			$code .= $this->getTopPagesCode ( $getTopPages );
		}
		
		$getTopReferrers = $this->getDataTopEntities ( $service, 'sessionSource', 'screenPageViews', $dateRanges, $request->property );
		if ($getTopReferrers) {
			$code .= $this->getTopReferrers ( $getTopReferrers );
		}
		
		$getTopSystems = $this->getDataTopEntities ( $service, array('browser', 'operatingSystemWithVersion'), 'sessions', $dateRanges, $request->property );
		if ($getTopSystems) {
			$code .= $this->getTopSystems ( $getTopSystems );
		}
		
		$getTopSearches = $this->getDataTopEntities ( $service, 'country', 'screenPageViews', $dateRanges, $request->property );
		if ($getTopSearches) {
			$code .= $this->getTopSearches ( $getTopSearches );
		}
		
		$code .= $this->getWindowResize() .
		("<span class='badge bg-primary mb-2'>" . Text::sprintf('COM_JREALTIME_PROPERTY_ID', '<span class="badge badge-gadash">' . $propertyID . '</span>') . "</span>") .
		($this->hasOwnCredentials ? null : "<span data-bs-content='" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET_DESC') . "' class='badge bg-warning hasPopover google pull-right'>" . Text::_('COM_JREALTIME_GOOGLE_APP_NOTSET') . "</span>") .
		'<div id="ga-dash">' .
		$this->getPeriodButtons($gaperiod) .
		'<div class="card card-info card-group-google" id="jrealtime_googlegraph_accordion">
			<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_graph">
				<h4><span class="fas fa-chart-area" aria-hidden="true"></span> ' . Text::_ ('COM_JREALTIME_GOOGLE_STATS' ) . '</h4>
			</div>
			<div id="jrealtime_googlestats_graph" class="card-body card-block accordion-chart" >' .
			$this->getMetricButtons($gaquery, 'totalUsers', 'screenPageViews', 'engagementRate', 'sessionsPerUser', 'sessions') .
			'<div id="gadash_div" style="height:350px;"></div>
				<table class="gatable" cellpadding="4" width="100%" align="center">
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (isset($textualData[0]) ? $textualData[0]->value : '-') . '</td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_VISITORS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (isset($textualData[1]) ? $textualData[1]->value : '-') . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGE_VIEWS' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (isset($textualData[2]) ? $textualData[2]->value : '-') . '</a></td>
					</tr>
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_ENGAGEMENTRATE' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (isset($textualData[3]) ? round ( $textualData[3]->value, 2 ) : '0') . '%</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_METRIC_SESSIONSPERUSER' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . (isset($textualData[4]) ? round ( $textualData[4]->value, 2 ) : '-') . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_ ( 'COM_JREALTIME_GOOGLE_PAGES_VISIT' ) . ':</span>
						<a href="javascript:void(0);" class="gatable">' . ((isset($textualData[2]) && isset($textualData[0]) && $textualData[0]->value) ? round ( $textualData[2]->value / $textualData[0]->value, 2 ) : '0') . '</a></td>
					</tr>
				</table>
			</div>
		</div>';
			
		$multiReports = $this->getMultiReports ('COM_JREALTIME_GOOGLE_MAP', 'COM_JREALTIME_GOOGLE_TRAFFIC', 'COM_JREALTIME_GOOGLE_REFERRERS', 'COM_JREALTIME_GOOGLE_COUNTRIES', 'COM_JREALTIME_GOOGLE_SYSTEMS', 'COM_JREALTIME_GOOGLE_PAGES', 'fa-globe');
		$code .= $multiReports;
		$code .= '</div>';
			
		return $code;
	}

	/**
	 * Main get data method for Matomo dashboard.
	 *
	 * @access public
	 * @return mixed
	 */
	public function getDataMatomo() {
		$params = $this->getComponentParams();
		$matomoSiteId = (int)$params->get('matomo_analytics_idsite', 0);
		
		$gaquery = $this->app->getInput()->get('gaquery', 'sessions');
		if(!in_array($gaquery, array('totalUsers', 'screenPageViews', 'engagementRate', 'sessionsPerUser', 'sessions'))) {
			$gaquery = 'sessions';
		}
		
		$gaperiod = $this->app->getInput()->get('gaperiod', 'last30days');
		extract($this->getPeriod($gaperiod, true));
		$title = $this->getTitle($gaquery);
		$dateRange = $from . ',' . $to;
		$maxResults = 12;
		
		try {
			$dailyReport = $this->callMatomoApi('VisitsSummary.get', array(
					'period' => 'day',
					'date' => $dateRange
			));
			$summaryReport = $this->callMatomoApi('VisitsSummary.get', array(
					'period' => 'range',
					'date' => $dateRange
			));
			$visitFrequency = $this->callMatomoApi('VisitFrequency.get', array(
					'period' => 'range',
					'date' => $dateRange
			));
		} catch (\Exception $e) {
			$this->app->enqueueMessage($e->getMessage(), 'error');
			return null;
		}
		
		$gadashData = '';
		if(is_array($dailyReport) && isset($dailyReport['nb_visits'])) {
			$metricValue = round($this->getMatomoMetricValue($dailyReport, $gaquery), 2);
			$gadashData .= "['" . $from . "'," . $metricValue . "],";
		} elseif(is_array($dailyReport)) {
			foreach($dailyReport as $dateLabel => $singleDayData) {
				if(!is_array($singleDayData)) {
					continue;
				}
				$metricValue = round($this->getMatomoMetricValue($singleDayData, $gaquery), 2);
				$dateLabel = str_replace("'", "\\'", (string)$dateLabel);
				$gadashData .= "['" . $dateLabel . "'," . $metricValue . "],";
			}
		}
		$gadashData = rtrim($gadashData, ',');
		if(!$gadashData) {
			$gadashData = "[0,0]";
		}
		
		$visits = isset($summaryReport['nb_visits']) ? (float)$summaryReport['nb_visits'] : 0.0;
		$uniqueVisitors = isset($summaryReport['nb_uniq_visitors']) ? (float)$summaryReport['nb_uniq_visitors'] : 0.0;
		$pageViews = isset($summaryReport['nb_pageviews']) ? (float)$summaryReport['nb_pageviews'] : 0.0;
		$bounceRate = isset($summaryReport['bounce_rate']) ? (float)$summaryReport['bounce_rate'] : 0.0;
		$engagementRate = max(0, 100 - $bounceRate);
		$sessionsPerUser = $uniqueVisitors > 0 ? ($visits / $uniqueVisitors) : 0.0;
		
		$textualData = array(
				$visits,
				$uniqueVisitors,
				$pageViews,
				$engagementRate,
				$sessionsPerUser
		);
		
		$code = $this->getFirstStepCode($title, $gadashData, $showevery);
		
		try {
			$visitsByCountryRows = $this->callMatomoApi('UserCountry.getCountry', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
			$visitsByCountry = $this->getMatomoRowsData($visitsByCountryRows, 'label', 'nb_visits', $maxResults);
			if($visitsByCountry) {
				$code .= $this->getVisitsByCountryCode($visitsByCountry);
			}
		} catch (\Exception $e) {
		}
		
		try {
			$trafficSourcesRows = $this->callMatomoApi('Referrers.getReferrerType', array(
					'period' => 'range',
					'date' => $dateRange
			));
			$trafficSources = $this->getMatomoRowsData($trafficSourcesRows, 'label', 'nb_visits', $maxResults);
			
			$newVisitors = isset($visitFrequency['nb_new_visits']) ? (float)$visitFrequency['nb_new_visits'] : 0.0;
			$returningVisitors = isset($visitFrequency['nb_returning_visits']) ? (float)$visitFrequency['nb_returning_visits'] : 0.0;
			$getNewReturnVisitors = "['" . Text::_('COM_JREALTIME_GOOGLE_NEW_VISITORS') . "'," . round($newVisitors, 2) . "],";
			$getNewReturnVisitors .= "['" . Text::_('COM_JREALTIME_GOOGLE_RETURNING_VISITORS') . "'," . round($returningVisitors, 2) . "]";
			
			if($trafficSources && $getNewReturnVisitors) {
				$trafficSources = StringHelper::str_ireplace('(none)', 'direct', $trafficSources);
				$code .= $this->getTrafficCode($trafficSources, $getNewReturnVisitors);
			}
		} catch (\Exception $e) {
		}
		
		try {
			$topPagesRows = $this->callMatomoApi('Actions.getPageTitles', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
			$topPages = $this->getMatomoRowsData($topPagesRows, 'label', 'nb_hits', $maxResults);
			if($topPages) {
				$code .= $this->getTopPagesCode($topPages);
			}
		} catch (\Exception $e) {
		}
		
		try {
			$topReferrersRows = $this->callMatomoApi('Referrers.getWebsites', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
			$topReferrers = $this->getMatomoRowsData($topReferrersRows, 'label', 'nb_visits', $maxResults);
			if($topReferrers) {
				$code .= $this->getTopReferrers($topReferrers);
			}
		} catch (\Exception $e) {
		}
		
		$topSystems = $this->getMatomoTopSystemsData($dateRange, $maxResults);
		if($topSystems) {
			$code .= $this->getTopSystems($topSystems);
		}
		
		try {
			$topCountriesRows = $this->callMatomoApi('UserCountry.getCountry', array(
					'period' => 'range',
					'date' => $dateRange,
					'filter_limit' => $maxResults
			));
			$topCountries = $this->getMatomoRowsData($topCountriesRows, 'label', 'nb_visits', $maxResults);
			if($topCountries) {
				$code .= $this->getTopSearches($topCountries);
			}
		} catch (\Exception $e) {
		}
		
		$code .= $this->getWindowResize() .
		("<span class='badge bg-primary mb-2 badge-gaproperty'>" . Text::sprintf('COM_JREALTIME_MATOMO_SITE_ID', '<span class="badge badge-gadash">' . $matomoSiteId . '</span>') . "</span>") .
		"<span class='badge bg-info mb-2 ms-2'>" . Text::_('COM_JREALTIME_MATOMO_DATA_SOURCE_BADGE') . "</span>" .
		'<div id="ga-dash">' .
		$this->getPeriodButtons($gaperiod) .
		'<div class="card card-info card-group-google" id="jrealtime_googlegraph_accordion">
			<div class="card-header accordion-toggle accordion_lightblue" data-bs-toggle="collapse" data-bs-target="#jrealtime_googlestats_graph">
				<h4><span class="fas fa-chart-area" aria-hidden="true"></span> ' . Text::_('COM_JREALTIME_GOOGLE_STATS') . '</h4>
			</div>
			<div id="jrealtime_googlestats_graph" class="card-body card-block accordion-chart" >' .
			$this->getMetricButtons($gaquery, 'totalUsers', 'screenPageViews', 'engagementRate', 'sessionsPerUser', 'sessions') .
			'<div id="gadash_div" style="height:350px;"></div>
				<table class="gatable" cellpadding="4" width="100%" align="center">
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_VISITS') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[0] . '</td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_VISITORS') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[1] . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_PAGE_VIEWS') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . $textualData[2] . '</a></td>
					</tr>
					<tr>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_METRIC_ENGAGEMENTRATE') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . round($textualData[3], 2) . '%</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_METRIC_SESSIONSPERUSER') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . round($textualData[4], 2) . '</a></td>
						<td width="33%"><span class="badge bg-secondary">' . Text::_('COM_JREALTIME_GOOGLE_PAGES_VISIT') . ':</span>
						<a href="javascript:void(0);" class="gatable">' . ($textualData[0] ? round($textualData[2] / $textualData[0], 2) : '0') . '</a></td>
					</tr>
				</table>
			</div>
		</div>';
			
		$multiReports = $this->getMultiReports('COM_JREALTIME_GOOGLE_MAP', 'COM_JREALTIME_GOOGLE_TRAFFIC', 'COM_JREALTIME_GOOGLE_REFERRERS', 'COM_JREALTIME_GOOGLE_COUNTRIES', 'COM_JREALTIME_GOOGLE_SYSTEMS', 'COM_JREALTIME_GOOGLE_PAGES', 'fa-globe');
		$code .= $multiReports;
		$code .= '</div>';
			
		return $code;
	}

	/**
	 * Return the google token
	 *
	 * @access public
	 * @return string
	 */
	public function getToken() {
		$clientID = (int)$this->app->getClientId();
		try {
			$query = "SELECT token FROM #__realtimeanalytics_google WHERE id = " . $clientID;
			$this->dbInstance->setQuery ( $query );
			$result = $this->dbInstance->loadResult ();
		} catch ( JRealtimeException $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), $e->getExceptionLevel () );
			$result = null;
		} catch ( \Exception $e ) {
			$jrealtimeException = new JRealtimeException ( $e->getMessage (), 'error' );
			$this->app->enqueueMessage ( $jrealtimeException->getMessage (), $jrealtimeException->getExceptionLevel () );
			$result = null;
		}
		return $result;
	}
	
	/**
	 * Return select lists used as filter for editEntity
	 *
	 * @access public
	 * @param Object $record
	 * @return array
	 */
	public function getLists($record = null): array {
		$lists = [];
		return $lists;
	}
	
	/**
	 * Return select lists used as filter for listEntities
	 *
	 * @access public
	 * @return array
	 */
	public function getFilters(): array {
		$filters = [];
		return $filters;
	}
	
	/**
	 * Delete entity
	 *
	 * @param array $ids
	 * @access public
	 * @return bool
	 */
	public function deleteEntity($ids): bool {
		return $this->deleteToken();
	}
}