I am working on a project using the Google Analytics API for PHP.
Here is what I need to do: We have about 320 client websites that we track in our Google Analytics account. We are running into an issue where the Google Analytics code stops tracking (for various reasons) on some sites, and we would like to catch this issue before our clients catch it.
So, what I would like to accomplish is writing a script that checks all of our client sites within Google Analytics and calls the Reporting API and queries for the number of sessions for the last seven days. If there is no data over the last 7 days, the code is more than likely not tracking properly on the website.
I have created a new API project in Google and obtained a client email that I have added as a user with view permissions to each of my accounts within Google Analytics. I have a total of 63 accounts in my Google Analytics organized by state (New York Clients, Virginia Clients, Oregon Clients etc) and each of the accounts have multiple sub-accounts/profiles under each for a total of 320.
I have a script that is working, but it is taking a really long time to run. This script does work, as it gives me each profile in my Google Analytics that hasn’t had data over the last 7 days, but it takes about 65 seconds to run and sometimes I get a timeout error because of how long it is taking to run. I’m certain that this is because of the foreach loops that are making a TON of calls to the API. I feel like if I reduced the number of times I am calling the API, the script would run much faster. However, I’m not sure how I can code my script so that it makes fewer calls to the API.
Is there any way that I could speed up the script below by making fewer calls to the reporting API? Maybe I am going about this the wrong way and there is a more simple way to do what I want to accomplish?
Here is my working code currently:
// Load the Google API PHP Client Library.
require_once __DIR__ . '/vendor/autoload.php';
//initialize analytics
$analytics = initializeAnalytics();
//an array that I will be using below in the getProfileIds() function
$ProfileIDWithDomain = array();
//Calls a function to obtain all of the profile IDs to query against the Core reporting API
$profiles = getProfileIds($analytics);
//for each profile in the $ProfileIDWithDomain array query the reporting API to get data over
last 7 days:
foreach ($profiles as $key => $value){
//$key is profile id $value is domain
$results = getResults($analytics, $key);
//print the results
printResults($results,$key,$value);
}
function initializeAnalytics()
{
// Creates and returns the Analytics Reporting service object.
// Use the developers console and download your service account
// credentials in JSON format. Place them in this directory or
// change the key file location if necessary.
$KEY_FILE_LOCATION = __DIR__ . '/{KEY_FILE}';
// Create and configure a new client object.
$client = new Google_Client();
$client->setApplicationName("GA Analytics Reporting");
$client->setAuthConfig($KEY_FILE_LOCATION);
$client->setScopes(['https://www.googleapis.com/auth/analytics.readonly']);
$analytics = new Google_Service_Analytics($client);
return $analytics;
}
function getResults($analytics, $profileId) {
// Calls the Core Reporting API and queries for the number of sessions
// for the last seven days.
return $analytics->data_ga->get(
'ga:' . $profileId,
'7daysAgo',
'today',
'ga:sessions');
}
function printResults($results, $profile,$domain) {
// Parses the response from the Core Reporting API and prints
// the profile name and total sessions.
if (count($results->getRows()) < 1) {
echo "<div class='item'>";
print "No results found for $profile, domain name: $domain";
echo "</div>";
}
}
function getProfileIds($analytics) {
// Get the list of accounts for the authorized user.
$accounts = $analytics->management_accounts->listManagementAccounts();
if (count($accounts->getItems()) > 0) {
//get all 63 accounts in GA
$items = $accounts->getItems();
//array to store accounts
$AccountIds = array();
foreach ($items as $item) {
//for each of the 63 accounts, store the account ID in an array
$AccountIds[] = $item->getId();
}
//now for each Account ID, we will obtain the properties
foreach ($AccountIds as $id){
// Get the list of properties for the authorized user.
$properties = $analytics->management_webproperties->listManagementWebproperties($id);
//if there are more than on item in the properties list (multuple profiles under it)
if (count($properties->getItems()) > 0) {
$items = $properties->getItems();
$i = 0;
if(count($items) > 1){
foreach($items as $item){
//for each item in the property list, get the id
$currentPropertyID = $item->getId();
//list management profiles for the id
$profiles = $analytics->management_profiles->listManagementProfiles($id, $currentPropertyID);
if (count($profiles->getItems()) > 0) {
$user = $profiles->getItems();
// Store the ID with an associated URL/domain name
$ProfileIDWithDomain[$user[0]->getId()] = str_replace($removeChar, "", $items[$i]["websiteUrl"]);
} else {
throw new Exception('No views (profiles) found for this user.');
}
$i++;
}
}
else{
//only one item in the properties list
$currentPropertyID = $items[0]->getId();
//list management profiles for the id
$profiles = $analytics->management_profiles->listManagementProfiles($id, $currentPropertyID);
if (count($profiles->getItems()) > 0) {
$user = $profiles->getItems();
// Store the ID with an associated URL/domain name
$ProfileIDWithDomain[$user[0]->getId()] = str_replace($removeChar, "", $items[0]["websiteUrl"]);
} else {
throw new Exception('No views (profiles) found for this user.');
}
}
} else {
throw new Exception('No properties found for this user.');
}
}
} else {
throw new Exception('No accounts found for this user.');
}
//return an associative array with ID => URL/Domain name for each profile
return $ProfileIDWithDomain;
}
You are doing a lot of calls there.
List all accounts
List all web properies in each account
List all views in each web propertie.
You can get the same results back with one call to account summaries list
Something like this.
$service->accountSummaries->ListAccountSummaries($optParams);
+1 on using account summaries.
You might also reduce the number of calls by using the newer Analytics Reporting API v4
The reports.batchGet method can take 5 requests at a time. However I don't know if it is any faster than the v3 method you are using now.
Related
Context
I am trying to make webservice that fetches the name and email from an users Apple account and place a Song or Artist in his library.
For adding a Song to the library I found this apple-music-api. library. To make requests on behalf of a user you need to request a user token with Apple MusicKit JS library.
For fetching the name and email of the user I use this oauth2 client that uses the signin with Apple functionality.
Problem
A Using the apple music kit... I can not query any user profile data. At least I cannot seem to find an example nor any documentation of this. Is there a possibility to get the user email and name using this route?
B Using the Sign in with Apple oauth flow I receive an access token which contains the name and email. But I cannot use the token to query the apple music api. It seems their scopes are limited to the name and email...and no scope for the music api or related seems to exist. Is there a possibility to get an user token that can be used on the music api?
C Are there any other possibilities to accomplish this without requiring the user to sign in twice on apple (once for the email and once for pushing the Song to his library)
What I tried for option B
// $leeway is needed for clock skew
Firebase\JWT\JWT::$leeway = 60;
$provider = new League\OAuth2\Client\Provider\Apple([
'clientId' => 'com.myapp.www',
'teamId' => 'team.id', // 1A234BFK46 https://developer.apple.com/account/#/membership/ (Team ID)
'keyFileId' => 'key.id', // 1ABC6523AA https://developer.apple.com/account/resources/authkeys/list (Key ID)
'keyFilePath' => dirname(__FILE__) . '/AuthKey_key.id.p8', // __DIR__ . '/AuthKey_1ABC6523AA.p8' -> Download key above
'redirectUri' => PLUGIN_URL . 'callback-apple-music.php',
]);
if (isset($_POST['code'])) {
if (empty($_POST['state']) || !isset($_COOKIE['apple-oauth2state']) || ($_POST['state'] !== $_SESSION['apple-oauth2state'])) {
unset($_COOKIE['apple-oauth2state']);
exit('Invalid state');
} else {
try {
// Try to get an access token (using the authorization code grant) via signin_with_apple
/** #var AppleAccessToken $token */
$token = $provider->getAccessToken('authorization_code', [
'code' => $_POST['code']
]);
$access_token = $token->getToken();
// create an client for api.music.apple
$tokenGenerator = new PouleR\AppleMusicAPI\AppleMusicAPITokenGenerator();
$jwtToken = $tokenGenerator->generateDeveloperToken(
'team.id',
'key.id',
dirname(__FILE__) .'/AuthKey_key.id.p8'
);
// create a developer token again
$curl = new \Symfony\Component\HttpClient\CurlHttpClient();
$client = new PouleR\AppleMusicAPI\APIClient($curl);
$client->setDeveloperToken($jwtToken);
$api = new PouleR\AppleMusicAPI\AppleMusicAPI($client);
$api->setMusicUserToken($access_token);
// This endpoint needs authorisation
$result = $api->getAllLibraryPlaylists(); //https://api.music.apple.com/v1/me/library/playlists?offset=0&limit=25
echo '<pre>';
print_r($result);
echo '</pre>';
// wp_redirect($redirect_url);
exit;
} catch (Exception $e) {
echo '<pre>';
print_r($e);
echo '</pre>';
}
}
}
The problem with the question is that these are three questions - and not telling which client.
Most commonly "login with" is only good for creating local accounts without much typing.
And it is quite likely intentional, that the oAuth2 scope is extremely limited for this purpose.
And I've looked it up ...one needs a "Music User Token":
https://developer.apple.com/documentation/applemusicapi/getting_keys_and_creating_tokens
And this token needs to be passed as HTTP header: 'Music-User-Token: [music user token]'.
Which means, that the user token may either originate from an iOS device (you'd need to expose eg. a REST API, so that it can be posted and then used by PHP as HTTP header, on the server-side): https://developer.apple.com/documentation/storekit/skcloudservicecontroller/2909079-requestusertoken (this only requires a login to your own API).
When running Apple MusicKit JS on the cient-side (browser), two logins may not be evitable:
https://developer.apple.com/documentation/musickitjs/musickit/musickitinstance/2992701-authorize
It makes no sense to use both of these flows within the same method(which also ignores the principle of single responsibility).
How can I get locations list in Google My Business API. Where I retrieved account list but I can't figure out how to retrieve location.
Here is my code where I am getting accounts list
define('GOOGLE_CLIENT_ID', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
define('GOOGLE_CLIENT_SECRET', 'XXXXXXXXXXXXX');
// Create Client Request to access Google API
$client = new Client();
$client->setApplicationName('my-app');
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$client->setRedirectUri('https://example.com/callback');
$client->addScope('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/business.manage');
$client->setAccessType('offline'); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setAccessToken($accessToken);
$service = new \Google_Service_MyBusinessAccountManagement($client);
$accounts = $service->accounts->listAccounts()->getAccounts(); // get accounts
To get google locations,
you can use this PHP My Business file to make things little easier.
First change you scope to ttps://www.googleapis.com/auth/plus.business.manage then include the file and create a object of Google_Service_MyBusiness with you client and then do like this.
$mybusinessService = new Google_Service_MyBusiness($client);
// Get the first account in the accounts array
$accounts = $mybusinessService->accounts;
$accountsList = $accounts->listAccounts()->getAccounts();
$account = $accountsList[0];
// Get the first location in the locations array
$locations = $mybusinessService->accounts_locations;
$locationsList = $locations->listAccountsLocations($account->name)->getLocations();
$location = $locationsList[0];
var_export($location);
With this process you can also able to get google reviews.
For more details check this Google Business API documentation.
Hope it can help
"It's correct!!!
I increase this at my code:
$optParams = array(
'readMask' => 'name',
);
$list_accounts_response = $my_business_account->accounts_locations->listAccountsLocations("accounts/114893266195214446586", $optParams);
var_dump($list_accounts_response);
Thank you.."
source : https://github.com/googleapis/google-api-php-client/issues/2213#issuecomment-1042785983
I am using Facebook API to fetch the full Ads list.
The Code is working, But it return only 25 Ad in case of i have 150+ Ad in my account.
I guess that happens because of the query limits on the Facebook API.
My Code:
$account = new AdAccount('act_<AD_ACCOUNT_ID>');
$account->read();
$fields_adset = array(
AdSetFields::ID,
AdSetFields::NAME,
AdSetFields::CAMPAIGN_ID,
AdSetFields::STATUS,
);
$ads = $account->getAds($fields_adset);
foreach ($ads as $adset) {
$adset_id = $adset->{AdSetFields::ID};
echo $adset_id;
//print_r($adset);
//exit();
}
So, they mentioned in the documentation that :
Use Asynchronous Requests to query a huge amount of data
Reference (1) : https://developers.facebook.com/docs/marketing-api/best-practices/
Reference (2) : https://developers.facebook.com/docs/marketing-api/insights/best-practices/#asynchronous
But, I can't apply that "Asynchronous" requests to my code to fetch the Full Ad List,
Please help me to fetch the full Ads list
Thank you.
You should implement pagination (or request a limit more high). With the PHP SDK you can implement the cursor as described in the doc here or more simply set the Implicit Fetching, as example:
..
use FacebookAds\Cursor;
...
Cursor::setDefaultUseImplicitFetch(true);
$account = new AdAccount('act_<AD_ACCOUNT_ID>');
$account->read();
$fields_adset = array(
AdSetFields::ID,
AdSetFields::NAME,
AdSetFields::CAMPAIGN_ID,
AdSetFields::STATUS,
);
$ads = $account->getAds($fields_adset);
foreach ($ads as $adset) {
$adset_id = $adset->{AdSetFields::ID};
echo $adset_id;
//print_r($adset);
//exit();
}
Hope this help
I'm creating a dashboard for myself that helps me keep track of the Facebook ads I'm running.
What I've not been able to figure out is:
How can I retrieve an array of ad IDs for all ads that are active or could soon be active after no further action on my part?
In other words, I want all ads that I've set to Active and that exist within Adsets and Campaigns that are active (and therefore these ads are live right now)... plus all the ads that from my perspective are Active but that Facebook has set to another status such as Pending Review (and will soon set back to Active).
I have some code below, but the problem is that it also accidentally includes Pending ads that--once reviewed and approved by Facebook--will be inactive rather than active (because I've set them that way). And I do NOT want this type of ad to be included in my report.
My report should only show me ones where I'm actively spending money or have the potential to spend money as soon as FB approves them.
I think I understand the difference between configured_status and effective_status in AbstractArchivableCrudObjectFields, but I don't know that it's enough to help me because I have lots of ads set to Active that are within Adsets that are Inactive, and I don't want to see those listed in my report.
Any recommendations?
public function getActiveAdIds() {
$key = 'activeAdIds';
$adIdsJson = Cache::get($key);
if ($adIdsJson) {
$adIds = json_decode($adIdsJson);
} else {
$adsResponse = $this->getAdsByStatus([ArchivableCrudObjectEffectiveStatuses::ACTIVE, ArchivableCrudObjectEffectiveStatuses::PENDING_REVIEW]);
$ads = $adsResponse->data;
$adIds = [];
foreach ($ads as $ad) {
$adIds[] = $ad->id;
}
$adIdsJson = json_encode($adIds);
Cache::put($key, $adIdsJson, 1);
}
return $adIds;
}
public function getAdsByStatus($statuses) {
$params = [\FacebookAds\Object\Fields\AbstractArchivableCrudObjectFields::EFFECTIVE_STATUS => $statuses];
$adAccount = new AdAccount(self::ACT_PREPEND . $this->fbConfig['account_id']);
$cursor = $adAccount->getAds([], $params);
$response = $cursor->getResponse();
$jsonString = $response->getBody();
return json_decode($jsonString);
}
I get stats based on assets for my active campaigns. I have 119 ad accounts. This is php code which I used it for this purpose (any suggestion to improve it will be appreciated):
$fields = array(AdsInsightsFields::ACCOUNT_NAME,AdsInsightsFields::CAMPAIGN_ID,
AdsInsightsFields::CAMPAIGN_NAME, AdsInsightsFields::ADSET_ID,
AdsInsightsFields::ADSET_NAME,AdsInsightsFields::DATE_START,
AdsInsightsFields::DATE_STOP,AdsInsightsFields::REACH,
AdsInsightsFields::SPEND, AdsInsightsFields::IMPRESSIONS,
AdsInsightsFields::CLICKS, AdsInsightsFields::WEBSITE_CLICKS,
AdsInsightsFields::CALL_TO_ACTION_CLICKS,AdsInsightsFields::ACTIONS,
AdsInsightsFields::TOTAL_ACTIONS,AdsInsightsFields::CPC,
AdsInsightsFields::CPM,AdsInsightsFields::CPP,
AdsInsightsFields::CTR,AdsInsightsFields::OBJECTIVE,);
$params_c['date_preset'] = AdDatePresetValues::YESTERDAY;
$params_c['time_increment'] = 1;
$params_c['action_attribution_windows'] = array('1d_view', '28d_click');
$params_c['effective_status'] = AdStatusValues::ACTIVE;
$params_c['level'] = AdsInsightsLevelValues::ADSET;
$params_c['filtering'] = [array("field"=>"campaign.delivery_info",
"operator"=>"IN",
"value"=>array("active"))];
$params_c['fields']= $fields;
try{
// Initialize a new Session and instanciate an Api object
Api::init(self::api_key, self::secret_token, self::extended_token)->getHttpClient()->setCaBundlePath( $this->path_cert);
// The Api object is now available trough singleton
$api = Api::instance();
$user = new \FacebookAds\Object\Business($business_id);
$user->read(array(BusinessFields::ID));
//get all ad_account from Business
$accounts = $user->getAssignedAdAccounts(
array(
AdAccountFields::ID,
),
array('limit'=>1000,)
);
} catch (FacebookAds\Exception\Exception $ex) {
return $ex->getMessage();
}
if(isset($accounts) && ($accounts->count() > 0)):
do{
$ad_account = $accounts->current();
$adset_insights = $ad_account->getInsights($fields,$params_c);
do {
$adset_insights->fetchAfter();
} while ($adset_insights->getNext());
$adsets = $adset_insights->getArrayCopy(true);
}
while ($accounts->current());
endif;
If you include the adset{end_time} field in the query for the ad, you can assume that ad is not actually running if the end_time was in the past. This is how we get a base list of ads to query on.
The next step we take (which probably won't help you, unfortunately, but may help others) is building a batch of simple requests (one per ad) to see if there are any insights data for that day. If the response is an empty 'data' array, we can remove that ID from the ad list.
After we've reduced the size of the ad list with those two steps we can then make requests to run all of our breakdown reports. This method almost cut our API requests in half.
I have yet to find a way to do a "give me all ads that are for sure running this day" query in one step.
Edit:
I just found a better way to do this.... :
curl -G \
-d 'access_token=<ACCESS_TOKEN>' \
-d 'level=campaign' \
-d 'filtering=[{field:"ad.impressions",operator:"GREATER_THAN",value:0}]' \
'https://graph.facebook.com/v2.7/act_<ACCOUNT_ID>/insights'
This is the essential bit of PHP:
// Add subscription
$subscription = new Recurly_Subscription();
$subscription->plan_code = $planCode;
$subscription->currency = 'USD';
$subscription->quantity = 1;
if ($couponCode != "") { $subscription->coupon_code = $couponCode; }
$subscription->account = new Recurly_Account();
$subscription->account->account_code = $customerID;
$subscription->billing_info = new Recurly_BillingInfo();
$subscription->account->billing_info->token_id = $token;
$subscription->create();
When this code runs, $token has the tokenID created by an earlier call to recurly.token (...) with the billing info.
The account already exists on Recurly -- the account ID, first and last names, but no billing info. This is because we allow people to signup for a complimentary service before subscribing. So I want to create the subscription on the extant account. Initially, following the code examples, the create() call was subscription->account->create(). But that failed because the account existed already.
This sounds like an issue with the old PHP library, which did not support tokenization of billing information. An upgrade to the PHP client library should fix this issue.