JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
if (!defined('ABSPATH')) die('No direct access allowed');
/**
* Handles Analytics Commands
*
* @method array ga_checker()
* @method array get_access_token()
* @method array set_authorization_code()
*/
class UpdraftCentral_Analytics_Commands extends UpdraftCentral_Commands {
private $scope = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/analytics.readonly';
private $endpoint = 'https://accounts.google.com/o/oauth2/auth';
private $token_info_endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo';
private $access_key = 'updraftcentral_auth_server_access';
private $auth_endpoint;
private $client_id;
private $view_key = 'updraftcentral_analytics_views';
private $tracking_id_key = 'updraftcentral_analytics_tracking_id';
private $expiration;
/**
* Constructor
*/
public function __construct() {
$this->auth_endpoint = defined('UPDRAFTPLUS_GOOGLE_ANALYTICS_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLE_ANALYTICS_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics';
$this->client_id = defined('UPDRAFTPLUS_GOOGLE_ANALYTICS_CLIENT_ID') ? UPDRAFTPLUS_GOOGLE_ANALYTICS_CLIENT_ID : '306245874349-6s896c3tjpra26ns3dpplhqcl6rv6qlb.apps.googleusercontent.com';
// Set transient expiration - default for 24 hours
$this->expiration = 86400;
}
/**
* Checks whether Google Analytics (GA) is installed or setup
*
* N.B. This check assumes GA is installed either using "wp_head" or "wp_footer" (e.g. attached
* to the <head/> or somewhere before </body>). It does not recursively check all the pages
* of the website to find if GA is installed on each or one of those pages, but only on the main/root page.
*
* @return array $result An array containing "ga_installed" property which returns "true" if GA (Google Analytics) is installed, "false" otherwise.
*/
public function ga_checker() {
try {
// Retrieves the tracking code/id if available
$tracking_id = $this->get_tracking_id();
$installed = true;
// If tracking code/id is currently not available then we
// parse the needed information from the buffered content through
// the "wp_head" and "wp_footer" hooks.
if (false === $tracking_id) {
$info = $this->extract_tracking_id();
$installed = $info['installed'];
$tracking_id = $info['tracking_id'];
}
// Get access token to be use to generate the report.
$access_token = $this->_get_token();
if (empty($access_token)) {
// If we don't get a valid access token then that would mean
// the access has been revoked by the user or UpdraftCentral was not authorized yet
// to access the user's analytics data, thus, we're clearing
// any previously stored user access so we're doing some housekeeping here.
$this->clear_user_access();
}
// Wrap and combined information for the requesting
// client's consumption
$result = array(
'ga_installed' => $installed,
'tracking_id' => $tracking_id,
'client_id' => $this->client_id,
'redirect_uri' => $this->auth_endpoint,
'scope' => $this->scope,
'access_token' => $access_token,
'endpoint' => $this->endpoint
);
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Extracts Google Tracking ID from contents rendered through the "wp_head" and "wp_footer" action hooks
*
* @internal
* @return array $result An array containing the result of the extraction.
*/
private function extract_tracking_id() {
// Define result array
$result = array();
// Retrieve header content
ob_start();
do_action('wp_head');
$header_content = ob_get_clean();
// Extract analytics information if available.
$output = $this->parse_content($header_content);
$result['installed'] = $output['installed'];
$result['tracking_id'] = $output['tracking_id'];
// If it was not found, then now try the footer
if (empty($result['tracking_id'])) {
// Retrieve footer content
ob_start();
do_action('wp_footer');
$footer_content = ob_get_clean();
$output = $this->parse_content($footer_content);
$result['installed'] = $output['installed'];
$result['tracking_id'] = $output['tracking_id'];
}
if (!empty($result['tracking_id'])) {
set_transient($this->tracking_id_key, $result['tracking_id'], $this->expiration);
}
return $result;
}
/**
* Gets access token
*
* Validates whether the system currently have a valid token to use when connecting to Google Analytics API.
* If not, then it will send a token request based on the authorization code we stored during the
* authorization phase. Otherwise, it will return an empty token.
*
* @return array $result An array containing the Google Analytics API access token.
*/
public function get_access_token() {
try {
// Loads or request a valid token to use
$access_token = $this->_get_token();
if (!empty($access_token)) {
$result = array('access_token' => $access_token);
} else {
$result = array('error' => true, 'message' => 'ga_token_retrieval_failed', 'values' => array());
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Clears any previously stored user access
*
* @return bool
*/
public function clear_user_access() {
return delete_option($this->access_key);
}
/**
* Saves user is and access token received from the auth server
*
* @param array $query Parameter array containing the user id and access token from the auth server.
* @return array $result An array containing a "success" or "failure" message as a result of the current process.
*/
public function save_user_access($query) {
try {
$token = get_option($this->access_key, false);
$result = array();
if (false === $token) {
$token = array(
'user_id' => base64_decode(urldecode($query['user_id'])),
'access_token' => base64_decode(urldecode($query['access_token']))
);
if (false !== update_option($this->access_key, $token)) {
$result = array('error' => false, 'message' => 'ga_access_saved', 'values' => array());
} else {
$result = array('error' => true, 'message' => 'ga_access_saving_failed', 'values' => array($query['access_token']));
}
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Saves the tracking code/id manually (user input)
*
* @param array $query Parameter array containing the tracking code/id to save.
* @return array $result An array containing the result of the process.
*/
public function save_tracking_id($query) {
try {
$tracking_id = $query['tracking_id'];
$saved = false;
if (!empty($tracking_id)) {
$saved = set_transient($this->tracking_id_key, $tracking_id, $this->expiration);
}
$result = array('saved' => $saved);
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Retrieves any available access token either previously saved info or
* from a new request from the Google Server.
*
* @internal
* @return string $authorization_code
*/
private function _get_token() {
// Retrieves the tracking code/id if available
$tracking_id = $this->get_tracking_id();
$access_token = '';
$token = get_option($this->access_key, false);
if (false !== $token) {
$access_token = isset($token['access_token']) ? $token['access_token'] : '';
$user_id = isset($token['user_id']) ? $token['user_id'] : '';
if ((!empty($access_token) && !$this->_token_valid($access_token)) || (!empty($user_id) && empty($access_token) && !empty($tracking_id))) {
if (!empty($user_id)) {
$args = array(
'headers' => apply_filters('updraftplus_auth_headers', array())
);
$response = wp_remote_get($this->auth_endpoint.'?user_id='.$user_id.'&code=ud_googleanalytics_code', $args);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
} else {
if (is_array($response)) {
$body = json_decode($response['body'], true);
$token_response = array();
if (is_array($body) && !isset($body['error'])) {
$token_response = json_decode(base64_decode($body[0]), true);
}
if (is_array($token_response) && isset($token_response['access_token'])) {
$access_token = $token_response['access_token'];
} else {
// If we don't get any valid response then that would mean that the
// permission was already revoked. Thus, we need to re-authorize the
// user before using the analytics feature once again.
$access_token = '';
}
$token['access_token'] = $access_token;
update_option($this->access_key, $token);
}
}
}
}
}
return $access_token;
}
/**
* Verifies whether the access token is still valid for use
*
* @internal
* @param string $token The access token to be check and validated
* @return bool
* @throws Exception If an error has occurred while connecting to the Google Server.
*/
private function _token_valid($token) {
$response = wp_remote_get($this->token_info_endpoint.'?access_token='.$token);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
} else {
if (is_array($response)) {
$response = json_decode($response['body'], true);
if (!empty($response)) {
if (!isset($response['error']) && !isset($response['error_description'])) {
return true;
}
}
}
}
return false;
}
/**
* Parses and extracts the google analytics information (NEEDED)
*
* @internal
* @param string $content The content to parse
* @return array An array containing the status of the process along with the tracking code/id
*/
private function parse_content($content) {
$installed = false;
$gtm_installed = false;
$tracking_id = '';
$script_file_found = false;
$tracking_id_found = false;
// Pull google analytics script file(s)
preg_match_all('/<script\b[^>]*>([\s\S]*?)<\/script>/i', $content, $scripts);
for ($i=0; $i < count($scripts[0]); $i++) {
// Check for Google Analytics file
if (stristr($scripts[0][$i], 'ga.js') || stristr($scripts[0][$i], 'analytics.js')) {
$script_file_found = true;
}
// Check for Google Tag Manager file
// N.B. We are not checking for GTM but this check will be useful when
// showing the notice to the user if we haven't found Google Analytics
// directly being installed on the page.
if (stristr($scripts[0][$i], 'gtm.js')) {
$gtm_installed = true;
}
}
// Pull tracking code
preg_match_all('/UA-[0-9]{5,}-[0-9]{1,}/i', $content, $codes);
if (count($codes) > 0) {
if (!empty($codes[0])) {
$tracking_id_found = true;
$tracking_id = $codes[0][0];
}
}
// If we found both the script and the tracking code then it is safe
// to say that Google Analytics (GA) is installed. Thus, we're returning
// "true" as a response.
if ($script_file_found && $tracking_id_found) {
$installed = true;
}
// Return result of process.
return array(
'installed' => $installed,
'gtm_installed' => $gtm_installed,
'tracking_id' => $tracking_id
);
}
/**
* Retrieves the "analytics_tracking_id" transient
*
* @internal
* @return mixed Returns the value of the saved transient. Returns "false" if the transient does not exist.
*/
private function get_tracking_id() {
return get_transient($this->tracking_id_key);
}
/**
* Returns the current tracking id
*
* @return array $result An array containing the Google Tracking ID.
*/
public function get_current_tracking_id() {
try {
// Get current site transient stored for this key
$tracking_id = get_transient($this->tracking_id_key);
// Checks whether we have a valid token
$access_token = $this->_get_token();
if (empty($access_token)) {
$tracking_id = '';
}
if (false === $tracking_id) {
$result = $this->extract_tracking_id();
} else {
$result = array(
'installed' => true,
'tracking_id' => $tracking_id
);
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Clears user access from database
*
* @return array $result An array containing the "Remove" confirmation whether the action succeeded or not.
*/
public function remove_user_access() {
try {
// Clear user access
$is_cleared = $this->clear_user_access();
if (false !== $is_cleared) {
$result = array('removed' => true);
} else {
$result = array('error' => true, 'message' => 'user_access_remove_failed', 'values' => array());
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
}