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'
Related
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.
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
Some basic background: I help run a gaming channel on YouTube, and I'm building a utility (using PHP) to integrate the channel's content with a companion website. Our playlists are primarily "let's play" series ordered by publication date that follow chronological progress through various games, and I would like the website to display the "latest episode" from a select number of series.
I know that I can work my way to the last video by chaining calls to the following:
$youtubeService->playlistItems->listPlaylistItems(
"snippet",
array(
"playlistId" => $playlistId
"pageToken" => $nextPageToken
)
)
And simply grab the last item in the response set when $nextPageToken is unset.
However, this strikes me as incredibly inefficient--partly because I believe it eats away at my API request quota, but mostly because it's going to slow down the overall response time of the site. Neither of those are ideal.
It seems like there should be an easier way to grab the "latest" video in a playlist either by changing the order of the response, or with some handy function, but I can't find any documentation on it.
I've looked at using the Search functions over the PlaylistItems, but (according to the documentation), Search only accepts Channel IDs as a parameter and not Playlist IDs, which makes me think that its the wrong direction to head.
The short answer here is that this appears to be impossible under the current version of the API. There is no apparent way to essentially select videos in reverse, but I did make a minor change which resulted in whole process being a tad more efficient.
This is the original code:
$items = $youtube->playlistItems->listPlaylistItems(
"snippet",
array(
"playlistId" => $playlistId,
"maxResults" => 50
)
);
while ($items->nextPageToken) {
$items = $youtube->playlistItems->listPlaylistItems(
"snippet",
array(
"playlistId" => $playlistId,
"maxResults" => 50,
"pageToken" => $items->nextPageToken
)
);
}
if ($items) {
return end($items->getItems());
}
This is the fix:
First, I added an object to assist with caching:
class PlaylistCache {
protected $expirationDate;
protected $playlistId;
protected $latestEpisode;
__construct($playlistId, $latestEpisode) {
$this-playlistId = $playlistId;
$this->latestEpisode = $latestEpisode;
$this->expirationDate = time() + 86400;
// get current time + 24 hours
}
public function getLatestEpisode() {
return $this->latestEpisode;
}
public function getPlaylistId() {
return $this->playlistId;
}
public function isExpired() {
return $this->expirationDate < time();
}
}
Then, before polling the API, I look to see if I have a cached version available, and I only resort to the API if that cached version is expired.
$playlistCache = json_decode(get_option('playlist_cache_' . $playlistId));
if ($playlistCache->isExpired()) {
$items = $youtube->playlistItems->listPlaylistItems(
"id",
array(
"playlistId" => $playlistId,
"maxResults" => 50
)
);
while ($items->nextPageToken) {
$items = $youtube->playlistItems->listPlaylistItems(
"id",
array(
"playlistId" => $playlistId,
"maxResults" => 50,
"pageToken" => $items->nextPageToken
)
);
}
if ($items) {
$videoId = end($items->getItems()[0]->getId());
$video = $youtube->videos->listVideos("snippet", array('id' => $videoId))
$video = $video->getItems()[0];
$playlistCache = new PlaylistCache($playlistId, $video);
update_option('playlist_cache_' . $playlistId, json_encode($playlistCache)));
}
}
return $playlistCache->getLatestEpisode();
The other big change here is that my calls to listPlaylistItems() are requesting the id instead of the snippet.
According to the documentation, the snippet costs 2 units of the API quota while requests for the id are 0. So, I don't need to snag the snippet for every single item on every single page. I only need to grab the snippet of the final video in the results, which I can do with the more refined call to
$youtube->videos->listVideos()
With the addition of the PlaylistCache class I only reach out to the API when the cached version of the Playlist returns true on the $playlistCache->isExpired() call, so I only need to poll the entire playlist one time every 24 hours instead of 1 time every page load for every user.
It's still not exactly ideal, but as far as I can tell, it's the best option available right now.
Firstly, you need to get the channelId for the user via HTTP request:
Sample request:
https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername={0}&key={1}
where {0} is the USERNAME and key is you API key
Then, get the list of videos by calling 'PlaylistItems:list', it returns a collection of playlist items that match the API request parameters. You can retrieve all of the playlist items in a specified playlist or retrieve one or more playlist items by their unique IDs.
Sample request:
https://www.googleapis.com/youtube/v3/search?order=date&part=snippet&channelId={0}&key={1}
From there, you can create an array to get the last video in the playlist. Include max-results parameter, the max-results specifies the maximum number of results that included in the result set.
Typically, the latest video in a playlist in added to the front, not the end.
I'm working on Bright Local API ( https://tools.brightlocal.com/ ) to get reviews of a business from Yelp ,Google+ etc.
I got some code of this API from GitHub with some examples.So I just register a free account in Bright Local and try those examples to get Reviews.
Below code is used to fetch the reviews of some business.After running this code i got a job id.But I dont know how to get reviews using this Job id.
$profileUrls = array(
'https://plus.google.com/114222978585544488148/about?hl=en',
'https://plus.google.com/117313296997732479889/about?hl=en',
'https://plus.google.com/111550668382222753542/about?hl=en'
);
// setup API wrappers
$api = new Api(API_KEY, API_SECRET, API_ENDPOINT);
$batchApi = new BatchApi($api);
// Step 1: Create a new batch
$batchId = $batchApi->create();
if ($batchId) {
printf('Created batch ID %d%s', $batchId, PHP_EOL);
// Step 2: Add review lookup jobs to batch
foreach ($profileUrls as $profileUrl) {
$result = $api->call('/v4/ld/fetch-reviews', array(
'batch-id' => $batchId,
'profile-url' => $profileUrl,
'country' => 'USA'
));
if ($result['success']) {
printf('Added job with ID %d%s', $result['job-id'], PHP_EOL);
}
}
// Step 3: Commit batch (to signal all jobs added, processing starts)
if ($batchApi->commit($batchId)) {
echo 'Committed batch successfully.'.PHP_EOL;
}
}
Anybody knows how to get reviews using this API ?
Thanks in advance.
It looks like you're missing the final step which is to poll for results. Our system works by adding jobs to a queue and then processing those jobs in parallel. Having created a batch, added jobs to that batch and committed it you then need to set up a loop, or come back and check for results periodically, until you see that the batch is marked as "Finished" and all jobs have returned data.
To do this call:
$results = $batchApi->get_results($batchId); // repeat this call until complete
$results will contain "status" which will be marked as "Finished" once all jobs have finished processing as well as the actual results associated with each job.
Hmmmmm I worked on some PHP code that pulls stock levels from my supplier and inserts the stock level into the database based on the product's SKU. I've inserted it into the class.product.php file which contains all the code used for the individual product page. The issue I'm having is that when the product page loads, it doesn't show the updated inventory levels unless you hit refresh. I've moved the code all over the place and can't get it to update the database and have the updated number loaded before the page is displayed.
Even when placed before all other code, I still have to refresh the page to see the update. I don't know what else to do about this. I feel like perhaps, I don't truly understand how PHP loads code. I've been working on this every day for weeks. I tried running it as an include file, on a separate page, at the top, in the middle, all over the place.
In the class file, it looks like I have the code before it calls the code to display the stock levels, that's why I'm so confused as to why it won't load the updates.
Any thoughts on why I'm unable to see the changes unless I refresh the page?
Thanks!
PHP loads the content when you request it ,
so opening a page gets the content ONCE,
The thing you want to do to get data updated is have AJAX calls to a php function that return data in JSON or XML format
Here you can see some examples but consider googling around for more detailed examples.
The problem was my code was not running until after the code to get and display the product data because I was using info from the product data that was only being called once. So the product data had be be called first in order for my code to run. So to fix this, I had to create a new function that would get the sku and pass it to my code before the code that called the product data to be displayed on the page. I copied the existing function to get the product data, renamed it to GetRealTimeStockLevels and added my code to the bottom of it. I put the call for the function above the call for the product data and it worked like I wanted. I'm glad I got this worked out, now I can add the same feature to the checkout page.
Below is the function call at the start of the page and then the function I created to run my update code.
public function __construct($productid=0)
{
// Get the stock level from supplier and update the database
$this->_GetRealtimeStockLevels($productid);
// Load the data for this product
$this->_SetProductData($productid);
public function _GetRealtimeStockLevels($productid=0)
{
if ($productid == 0) {
// Retrieve the query string variables. Can't use the $_GET array
// because of SEO friendly links in the URL
SetPGQVariablesManually();
if (isset($_REQUEST['product'])) {
$product = $_REQUEST['product'];
}
else if(isset($GLOBALS['PathInfo'][1])) {
$product = preg_replace('#\.html$#i', '', $GLOBALS['PathInfo'][1]);
}
else {
$product = '';
}
$product = $GLOBALS['ISC_CLASS_DB']->Quote(MakeURLNormal($product));
$productSQL = sprintf("p.prodname='%s'", $product);
}
else {
$productSQL = sprintf("p.productid='%s'", (int)$productid);
}
$query = "
SELECT p.*, FLOOR(prodratingtotal/prodnumratings) AS prodavgrating, pi.*, ".GetProdCustomerGroupPriceSQL().",
(SELECT COUNT(fieldid) FROM [|PREFIX|]product_customfields WHERE fieldprodid=p.productid) AS numcustomfields,
(SELECT COUNT(reviewid) FROM [|PREFIX|]reviews WHERE revstatus='1' AND revproductid=p.productid AND revstatus='1') AS numreviews,
(SELECT brandname FROM [|PREFIX|]brands WHERE brandid=p.prodbrandid) AS prodbrandname,
(SELECT COUNT(imageid) FROM [|PREFIX|]product_images WHERE imageprodid=p.productid) AS numimages,
(SELECT COUNT(discountid) FROM [|PREFIX|]product_discounts WHERE discountprodid=p.productid) AS numbulkdiscounts
FROM [|PREFIX|]products p
LEFT JOIN [|PREFIX|]product_images pi ON (pi.imageisthumb=1 AND p.productid=pi.imageprodid)
WHERE ".$productSQL;
if(!isset($_COOKIE['STORESUITE_CP_TOKEN'])) {
// ISC-1073: don't check visibility if we are on control panel
$query .= " AND p.prodvisible='1'";
}
$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
$row = $GLOBALS['ISC_CLASS_DB']->Fetch($result);
if (!$row) {
return;
}
$this->_product = $row;
$this->_prodid = $row['productid'];
$this->_prodname = $row['prodname'];
$this->_prodsku = $row['prodcode'];
$GLOBALS['CurrentProductLink'] = ProdLink($this->_prodname);
$server_url = "http://ms.com/fgy/webservices/index.php";
$request = xmlrpc_encode_request("catalog.getStockQuantity", array($this->_prodsku));
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$file = file_get_contents($server_url, false, $context);
$response = xmlrpc_decode($file);
$query = sprintf("UPDATE [|PREFIX|]products SET prodcurrentinv='$response' where prodcode='%s'", $GLOBALS['ISC_CLASS_DB']->Quote($this->_prodsku));
$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
}