I want to push the products profit of a transaction to Google Analytics.
With this i want to generate a report where i can see the total Product revenue and it's profit for each product that has been ordered.
i'm using this api to push custom metrics to analytics (Measurement protocol api)
I have made several custom metrics in analytics, for example:
Productsprofit (Scope hit, decimal)
Productsprofit2 (Scope Product, decimal)
I've googled several times and i think i need to use the Scope Hit for this type of tracking, am i right?
I've used the api to push the metric with the right indexes to Analytics
$analytics->setTransactionId('test'.$orderId)
->setAffiliation('Testshop')
->setRevenue(number_format($orderTotalPrice,2,'.',''))
->setTax(number_format($taxTotal,2,'.',''))
->setShipping(number_format($shippingTotal,2,'.',''))
->setCouponCode('');
foreach($orderInfo['orderDetails'] as $orderDetail) {
// Include a product, only required fields are SKU and Name
$productData1 = [
'sku' => $orderDetail['model'],
'name' => $orderDetail['name'],
'brand' => '',
'category' => '',
'variant' => '',
'price' => number_format($orderDetail['price'], 2, '.', ''),
'quantity' => $orderDetail['aantal'],
'coupon_code' => '',
'position' => 0,
'custom_metric_3'=>50.45, //<-- test data (Hit)
'custom_metric_4'=>15.50 //<-- test data (Product)
];
$analytics->addProduct($productData1);
}
// Don't forget to set the product action, in this case to PURCHASE
$analytics->setProductActionToPurchase();
// Finally, you must send a hit, in this case we send an Event
$analytics->setEventCategory('Checkout')
->setEventAction('Purchase')
->sendEvent();
I've created a custom report and added these metric to my report. But both metrics stay empty in my report.
Does anyone know what i'm doing wrong ? or what might solve my problem?
Thanks in advance!
In the PHP library that you are using, custom metrics and dimensions are set in a different manner depending on the scope, for Product scope you use the product array like you did, for Hit scope you use the setCustomMetric method. In your code this would look like:
$analytics->setTransactionId('test'.$orderId)
->setAffiliation('Testshop')
->setRevenue(number_format($orderTotalPrice,2,'.',''))
->setTax(number_format($taxTotal,2,'.',''))
->setShipping(number_format($shippingTotal,2,'.',''));
foreach($orderInfo['orderDetails'] as $orderDetail) {
$productData1 = [
'sku' => $orderDetail['model'],
'name' => $orderDetail['name'],
'brand' => 'the brand',
'category' => 'test category',
'variant' => 'variant x',
'price' => number_format($orderDetail['price'], 2, '.', ''),
'quantity' => $orderDetail['qty'],
'coupon_code' => 'coupon used',
'custom_metric_4' => 15.50,
];
$analytics->addProduct($productData1);
}
$analytics->setProductActionToPurchase();
$analytics->setCustomMetric(50.45, 3);
$analytics->setEventCategory('Checkout')
->setEventAction('Purchase')
->sendEvent();
Notice the call to $analytics->setCustomMetric(50.45, 3); before sending the hit. This should send the right data, the rest is creating or looking at the right report in Google Analytics.
Related
I'm adding GTM and GA4 to some website apps that need to produce detailed stats on the click-throughs of ads per advertiser.
This looks infeasible with standard GA reporting so am using the PHP implementation of Analytics Data API V1 Beta. Since there are few examples (eg analyticsdata/quickstart.php) of V1 reporting using PHP, I am translating other classes and operands from the REST API’s JSON .
<?php
namespace Google\Analytics\Data\V1beta;
require 'vendor/autoload.php';
$property_id = '<redacted>';
putenv('GOOGLE_APPLICATION_CREDENTIALS=Keyfile.json');
$client = new BetaAnalyticsDataClient();
// Make an API call.
$response = $client->runReport([
'property' => 'properties/' . $property_id,
'dateRanges' => [
new DateRange([
'start_date' => '2021-04-01',
'end_date' => 'today',
]
),
],
'dimensions' => [new Dimension(
[
'name' => 'customEvent:link_classes'
]
),
],
'dimensionFilter'=>[new FilterExpression(
[
'filter'=>[new Filter(
[
'field_name' => 'customEvent:Classes',
'string_filter' => [new Filter\StringFilter(
[
'match_type'=> '1',
'value'=> 'AdvertA',
'case_sensitive'=> false
])]])]])],
'metrics' => [new Metric(
[
'name' => 'eventCount',
]
)
]
]);
etc
The Quickstart example works but has endless trouble when a dimensionFilter is added.
For example, match_type should be an enum of one of a few character strings (EXACT, CONTAINS and so on). The JSON definition of match_type only shows the strings (enum 'members') and not any associated values (which would usually be integers). The GA4 migration guide has an example with
"matchType": "BEGINS_WITH"
PHP doesn’t have ‘enum’ but the equivalent would be to select one string and assign it to match_type (vide above). Wrong: StringFilter falls over unless it is given an integer operand, presumably the ordinal number of the desired match in the enum match string (and is the first one 0 or 1?). My understanding of the JSON schema was that an 'enum' list simply restricted the result to one of the unique operands, with an optional check on the operand type. (By comparison, the Python enumerate function returns an object containing a list of pairs with the ordinal number of an operand preceding the operand).
Custom dimensions appear not to conform to the API’s JSON. In Analytics, I specify a custom dimension with a dimension Name of Classes and User Property/Parameter of link_classes**.
However... in the API, dimension Name has to be customEvent:link_classes and not customEvent:Classes. Otherwise it falls over with ‘Field customEvent:Classes is not a valid dimension’
This occurs also when defining field_name in a Filter within a Filter Expression.
So is the API dimension Name not the name of the Analytics dimension Name but actually the Property/Parameter of an Analytics descriptive name? In one place I read the latter: "Custom dimensions are specified in an API report request by the dimension's parameter name and scope." but elsewhere it is implied that Name is the dimension name, e.g. /devguides/reporting/data/v1/advanced:
"dimensions": [{ "name": "customUser:last_level" }]
Finally, even falling in line with what the developers have implemented, dimensionFilter falls over with ‘Expect Google\Analytics\Data\V1beta\Filter\StringFilter’
It is Beta code but one would not expect overt deviations from the REST spec so perhaps I am reading the spec wrongly. Does anyone else have this problem?
** GTM has a ‘Click - Just Links’ trigger where the ‘click URL’ ‘contains’ the advertiser’s URL. The Classes custom dimension in the API dimension Filter has the class values of the adverts click-through links.
To answer the first part of your question, I believe the correct way to use an enum in PHP would be:
'match_type' => Filter\StringFilter\MatchType::BEGINS_WITH
As for the second question. Per the API schema documentation the name of a custom dimension is constructed as customEvent:parameter_name for event scoped dimensions and customUser:parameter_name for user scoped dimensions.
Where parameter_name, as you correctly noted, is not a descriptive name, but rather the event parameter name. In your example you seem to be using a user scoped dimension, so the dimension name in the API should be customUser:link_classes.
Here is a complete example that seems to be running fine:
require 'vendor/autoload.php';
use Google\Analytics\Data\V1beta\BetaAnalyticsDataClient;
use Google\Analytics\Data\V1beta\DateRange;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\FilterExpression;
use Google\Analytics\Data\V1beta\Filter;
use Google\Analytics\Data\V1beta\Metric;
/**
* TODO(developer): Replace this variable with your Google Analytics 4
* property ID before running the sample.
*/
$property_id = 'YOUR-GA4-PROPERTY-ID';
$client = new BetaAnalyticsDataClient();
// Make an API call.
$response = $client->runReport([
'property' => 'properties/' . $property_id,
'dateRanges' => [
new DateRange([
'start_date' => '2020-03-31',
'end_date' => 'today',
]),
],
'dimensions' => [new Dimension(
[
'name' => 'customUser:link_classes'
]),
],
'dimensionFilter' => new FilterExpression(
[
'filter' => new Filter(
[
'field_name' => 'customUser:link_classes',
'string_filter' => new Filter\StringFilter(
[
'match_type' => Filter\StringFilter\MatchType::BEGINS_WITH,
'value' => 'AdvertA',
'case_sensitive' => false
]
)
])
]),
'metrics' => [new Metric(
[
'name' => 'eventCount',
]
)
]
]);
Many thanks Ilya for a most useful, prompt and correct reply.
Three points:
1.
Using
'match_type' => Filter\StringFilter\MatchType::BEGINS_WITH
instead of:
'match_type' => ‘BEGINS_WITH’
fixes the problem of “Uncaught Exception: Expect integer. in /vendor/google/protobuf/src/Google/Protobuf/Internal/GPBUtil.php” as the
MatchType::BEGINS_WITH (etc) constant returns an integer (in this case 2) from class MatchType.
2.
It would forestall errors if a reminder were added to the places in Custom Dimension documentation where dimension Name is used, such as
/devguides/reporting/data/v1/advanced: "dimensions": [{ "name": "customUser:last_level" }]
emphasising that name is not the dimension Name as defined to Analytics but rather the associated User Property/Parameter name. Or perhaps the Name heading in GA's Custom Dimension 'form' should be amended.
3.
Finally, dimensionFilter falling over with
‘Expect Google\Analytics\Data\V1beta\Filter\StringFilter’
error message was caused by my stupidity in instantiating FilterExpression, Filter and StringFilter as if they were array elements,
e.g
'string_filter' => [new Filter\StringFilter(
rather than
'string_filter' => new Filter\StringFilter(
something that is an unfortunately easy mistake to make when carelessly following the sample report code, where dateRanges, dimensions and so on correctly define arrays (since they can take multiple date ranges and dimensions), e.g.
'dimensions' => [new Dimension (
I am execting following GET request to obtain data form wordpress
http://domain:port/wp-json/wp/v2/announcement?_fields=announcement_category,effective_start_date,effective_end_date
The result is as below
[
{
"announcement_category": [
216
],
"effective_start_date": "2020-03-27",
"effective_end_date": "2020-04-16"
},
{
"announcement_category": [
215
],
"effective_start_date": "2020-03-25",
"effective_end_date": "2020-03-31"
}]
I need to sort/order my response in the ASC order of the "announcemet_category" value. (there is always one value for this array)
I know "orderby" does not support meta_values.
How to enable meta_value sorting and how can I execute my query for this.
Please help
You should be able to sort on a meta value using:
$q = new WP_Query(
// Other query params e.g. post type...
array(
'meta_key' => 'announcement_category',
'orderby' => 'meta_value_num',
'order' => 'ASC',
)
);
Based on this answer.
However I think your issue is that as you've pointed out, announcement_category is an array rather than a number, which you won't be able to sort on.
If you can't change announcement_category to a number rather than an array, you can use a usort to sort the results of your WordPress query, before sending the API response:
$arr1 = array(
array('announcement_category'=>array(216)),
array('announcement_category'=>array(218)),
array('announcement_category'=>array(202)),
array('announcement_category'=>array(300)),
);
function sortByOrder($a, $b) {
return $a['announcement_category'][0] - $b['announcement_category'][0];
}
usort($arr1, 'sortByOrder');
print_r($arr1); // Ordered ascendingly
Alternatively, you can use javascript on the client side to sort the response as required:
announcements.sort(function (a, b) {
return a[announcement_category][0] - b[announcement_category][0];
});
Array Sort
I am facing an issue in wordpress serialized data. I am developing a custom plugin which is in relation with woocommerce. I have added a checkout section in woocommerce settings section. Also I am providing same settings update form in my plugin section which is new menu option in left menu.
when I am saving data through woocommerce setting section it stores data in in wp_options table as serialized data. Below is example :
a:18:{s:7:"enabled";s:3:"yes";s:9:"test_mode";s:2:"no";s:19:"is_application_name";s:0:"";s:10:"is_api_key";s:0:"";s:17:"order_customtable";s:0:"";s:16:"order_customflds";s:0:"";s:23:"order_product_customfld";s:0:"";s:14:"is_merchant_id";s:0:"";s:5:"title";s:12:"Infusionsoft";s:9:"tax_label";s:9:"Sales Tax";s:16:"is_free_shipping";s:2:"no";s:11:"description";s:20:"Pay via
Infusionsoft";s:5:"cards";s:16:"VISA
MASTERCARD";s:14:"wooorderstatus";s:0:"";s:14:"thanks_message";s:39:"Thank
you. Your order has been
received";s:5:"debug";s:2:"no";s:11:"debug_email";s:0:"";s:13:"http_post_key";s:0:"";}
From my plugin page, on form submit I am getting field values and creating an array as below :
Array (
[enabled] => yes
[test_mode] => no
[is_application_name] =>
[is_api_key] =>
[order_customtable] =>
[order_customflds] =>
[order_product_customfld] =>
[is_merchant_id] =>
[title] => Infusionsoft
[tax_label] => Sales Tax
[is_free_shipping] => no
[description] => Pay via Infusionsoft
[cards] => VISA MASTERCARD
[wooorderstatus] =>
[thanks_message] => Thank you. Your order has been received
[debug] => no
[debug_email] =>
[http_post_key] => )
Now serializing and updating option using function update_option it will save data in in data base as below string :
s:597:"a:18:{s:7:"enabled";s:3:"yes";s:9:"test_mode";s:2:"no";s:19:"is_application_name";s:0:"";s:10:"is_api_key";s:0:"";s:17:"order_customtable";s:0:"";s:16:"order_customflds";s:0:"";s:23:"order_product_customfld";s:0:"";s:14:"is_merchant_id";s:0:"";s:5:"title";s:12:"Infusionsoft";s:9:"tax_label";s:9:"Sales Tax";s:16:"is_free_shipping";s:2:"no";s:11:"description";s:20:"Pay via
Infusionsoft";s:5:"cards";s:15:"VISA
MASTERCARD";s:14:"wooorderstatus";s:0:"";s:14:"thanks_message";s:39:"Thank
you. Your order has been
received";s:5:"debug";s:2:"no";s:11:"debug_email";s:0:"";s:13:"http_post_key";s:0:"";}";
Please help me in this issue.
Don't serialize the array yourself, update_option will do it if needed.
https://developer.wordpress.org/reference/functions/update_option/
In a Symfony 2.4+ project, what is the best way to register an array of values for a field that saves as an integer but needs to display human readable values in a template?
I have an entity with a property that is populated with integer values that represent different constant values:
/**
* The repetition frequency for billing cycle:
* #ORM\Column(type="smallint")
*/
protected $repetition = 0;
I would like to store the names of these values somewhere, so initially I put them in my entity with a getter:
protected $repetitionName = array(
0 => 'Setup',
1 => 'Second',
2 => 'Minute',
3 => 'Hour',
4 => 'Day',
5 => 'Week',
6 => 'Month',
7 => 'Year'
);
public function getRepetitionName() {
return $this->repetitionName;
}
This seems like a great central repository for the values.
Then in my twig template I don't want to display the integer, I want the corresponding name value. So I translate them like this:
<div class="billingCycle">{{ entity.repetitionName[entity.repetition] }}</div>
And in my form builder I make a field that references that array like this:
$builder->add('repetition', 'choice', array(
'label' => 'Billing Cycle',
'help' => 'The repetition frequency when this service is billed.',
'choices' => $builder->getData()->getRepetitionNamePer(),
// default to monthly (the most common)
'empty_data' => 6,
'required' => TRUE
));
The Problems With This Approach
1. Translation: If I want to translate this at any point, it's hard coded in one place.
2. Reusability: If I have other entities that have repetition (e.g. event calendar) it would be nice to reuse this.
3. Configurability: Ideally these would be editable in a config file instead of the entity code.
Alternative Solution: Custom Form Type as Global Service with Config Parameters
The better option seems to set some default parameters in the config file:
parameters:
gutensite_component.options.status:
0: Inactive
1: Active
gutensite_component.options.repetition:
0: Setup
1: Second
2: Minute
3: Hour
4: Day
5: Week
6: Month
7: Year
Then create a custom form type Gutensite\ComponentBundle\FormType\RepetitionType that loads the options from the config parameters. See the Documentation for a great example of this. Then just refer to that field type like this:
$builder->add('repetition', 'repetition', array(
'label' => 'Billing Cycle',
'help' => 'The repetition frequency when this service is billed.',
// default to monthly (the most common)
'empty_data' => 6,
'required' => TRUE
));
The inconvenient part of this solution, is that you have to either parameter to twig in your config (which is bloat for every template even if you don't need it), or always remember to manually pass the parameters to twig from your controller.
// I add to the standard object `$controller->view` which gets passed to Twig
$controller->view->options['repetition'] = $this->container->getParameter('gutensite_component.options.repetition');
To be accessed like:
<div class="priceValue label label-primary">${{ entity.price }}/<span class="billingCycle">{{ view.options.repetition[entity.repetition] }}</span></div>
This is more clunky than I would like but it is reusable. Maybe others have better solutions to pass the configuration to twig than what is represented here.
Other Suggestions?
Do you have any other suggestions, best practices or lessons learned? Please share.
What I would do is to save those values as objects of a new entity and set a manyToOne relation from your main entity (or any other entity as your calendar event entity) to the repetition entity
If so, you can easily add new repetitions every time you want or get all objects of the main entity using any specific repetition.
Also you can build a nice form for a new object of the main entity with an input (human readable) of repetition values:
$builder->add('repetition', 'entity', array(
'class' => 'AcmeDefaultBundle:Repetition', // This is your new 'repetition entity'
'property' => 'repetitionName', // The field of your 'repetition entity' that stores the name (the one that will show a human readable value instead of the id)
'expanded' => true, // True if you prefer checkbox instead of a dropdown list
'label' => 'Billing Cycle',
'help' => 'The repetition frequency when this service is billed.',
'required' => true,
));
Now when you want to show the name of the repetition in twig you will use:
<div class="billingCycle">{{ entity.Repetition.repetitionName }}</div> (you can also translate that value later)
A cleaner way (if you use it only for this entity) is to define a public method in your entity that returns you that array:
public function getRepetitionNames() {
return array(
0 => 'Setup',
1 => 'Second',
2 => 'Minute',
3 => 'Hour',
4 => 'Day',
5 => 'Week',
6 => 'Month',
7 => 'Year'
);
}
Another method to retrieve the label based by your "repetition" field value:
/**
* #param $key
* #return null
*/
function getRepetitionLabel(){
$repetitions = $this->getRepetitionNames();
return isset($repetitions[$this->repetition]) ? $repetitions[$this->repetition] : 0;
}
Will be much easier to access it in twig now:
<div class="billingCycle">{{ entity.repetitionLabel }}</div>
Last, in your form use your entity to retrieve the values:
$object = $builder->getData();
$repetitionChoices = $object->getRepetitionNames();
$builder->add('repetition', 'choice', array(
'label' => 'Billing Cycle',
'help' => 'The repetition frequency when this service is billed.',
'choices' => $repetitionChoices
'empty_data' => 6,
'required' => TRUE
));
If you plan to use it in more entities, you could use a interface.
I created the following token; however, when I try to use site:coupons as a data selector in a loop action
It does not appear in data selection browser. Note that it does appear as replacement pattern when i use for example "Show a message on the site" action.
I spent lot of time searching in the internet and rules' token' issue queue, i tried to read the source codes of core token , token and rules as well. I also found some information too like data selector are no tokens! or rules only works with entities!
So far i couldn't get this to work no matter hard i tried. My data is not entity. Is there anyway to integrate it with rules?
I couldn't find any official documentation on this so i created an issue with hope that some of the rule's experts can help me out.
Note : if i replace site with coupon-link in the following code, it won't even appear as replacement pattern in rules. but it works fine as token anywhere else but in rules
Thanks in advance
<?php
/**
* Implements hook_token_info().
*/
function coupon_link_token_info() {
$types['coupon-link'] = array(
'name' => t("Coupon link coupon info"),
'description' => t("Info about linked coupon via url."),
);
// Andy Pangus specific tokens.
$tokens['site']['coupon-code'] = array(
'name' => t("Coupon Link Coupon Code"),
'description' => t("The code of the coupon entered via url."),
);
$tokens['site']['coupon'] = array(
'name' => t("Coupon Link Coupon"),
'description' => t("The coupon entered via url."),
'type' => 'commerce_coupon'
);
$tokens['site']['coupons'] = array(
'name' => t("Coupon Link List Coupons"),
'description' => t("The coupons entered via url."),
'type' => 'array'
);
return array(
'types' => $types,
'tokens' => $tokens,
);
}
/**
* Implements hook_tokens().
*
* #ingroup token_example
*/
function coupon_link_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
// Text format tokens.
if ($type == 'site' && __coupon_link_get_coupon_code()) {
//$format = $data['format'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'coupon-code':
// Since {filter_format}.format is an integer and not user-entered
// text, it does not need to ever be sanitized.
$replacements[$original] = $sanitize ? filter_xss(__coupon_link_get_coupon_code()) : __coupon_link_get_coupon_code();
break;
case 'coupon':
// Since the format name is user-entered text, santize when requested.
$replacements[$original] = __coupon_link_get_coupon(__coupon_link_get_coupon_code());
break;
case 'coupons':
// Since the format name is user-entered text, santize when requested.
$replacements[$original] = array(__coupon_link_get_coupon(__coupon_link_get_coupon_code()));
break;
}
}
}
return $replacements;
}
?>
A few things.
Tokens are formatted as [type:token] as explained on the hook_token_info api page. For your example, it would be [coupon-link:coupon]. I'm not sure why you're appending your tokens to the site array, as your custom coupon token probably has nothing to do with sitewide tokens like *site_url* or *site_name*.
Because the types are machine names, you should change it to coupon_link as machine names with dashes are not Drupal standard.
If you truly get lost, I suggest also looking at the token example from the examples module.