I'm using the google analytics 4 API to retrieve the analytics data. While I'm having an issue to retrieve the Organic search data.
Working code for other metrics.
$response = $this->data_client->runReport([
'property' => $propertyId,
'dateRanges' => [new DateRange(['start_date' => '7daysAgo', 'end_date' => '1daysAgo'])],
'metrics' => [
new Metric(['name' => 'totalUsers']),
new Metric(['name' => 'newUsers']),
new Metric(['name' => 'sessions']),
new Metric(['name' => 'screenPageViews']),
new Metric(['name' => 'engagedSessions']),
new Metric(['name' => 'activeUsers']),
],
// 'dimensions' => [
// new Dimension(['name' => 'streamName']),
// ]
]);
foreach ($response->getRows() as $row) {
foreach ($row->getDimensionValues() as $dimensionValue) {
print 'Dimension Value: ' . $dimensionValue->getValue() . PHP_EOL;
}
foreach ($row->getMetricValues() as $metricValue) {
print 'Metric Value: ' . $metricValue->getValue() . PHP_EOL;
}
}
I'm trying to use the defaultChannelGrouping to retrieve the organic search but not getting any useful documents how to do.
Sample code which is not working.
$response = $this->data_client->runReport([
'property' => $propertyId,
'dateRanges' => [new DateRange(['start_date' => '7daysAgo', 'end_date' => '1daysAgo'])],
'dimensions' => [
new Dimension(['name' => 'defaultChannelGrouping']),
]
]);
foreach ($response->getRows() as $row) {
foreach ($row->getDimensionValues() as $dimensionValue) {
print 'Dimension Value: ' . $dimensionValue->getValue() . "<br/>";
}
}
Try using sessionDefaultChannelGrouping rather than defaultChannelGrouping.
In the migration guides, GA4 recommends using sessionDefaultChannelGrouping in GA4 where you might have used ga:channelGrouping in Universal Analytics.
defaultChannelGrouping is an attribution dimension. If your GA4 property's event attribution model is Data-driven, defaultChannelGrouping will return no rows if your GA4 property has not had a conversion in the date range.
Related
I'm working on a project where a user is able to upload a file. My code works when a single file is uploaded, but I need to change it so a user is able to upload multiple files.
I want to store the files in my database as String. Currently it is stored as example: "file1.png". When uploading multiple files I would like it to be stored as "file1.png;file2.png;file3.png".
However when I add the "multiple => true" in the form, I get an error when pressing submit by the validator that the input needs to be a String.
My best guess is that I need to use Data transformers, but after reading the docs I still don't know how to approach this. ?
Data Transform
This is the controller (currently it expects a single file, as for multiple I would use foreach):
\#\[Route('/new', name: 'app_blog_new', methods: \['GET', 'POST'\])\]
\#\[IsGranted('IS_AUTHENTICATED')\]
public function new(Request $request, BlogRepository $blogRepository, SluggerInterface $slugger, MailerInterface $mailer): Response
{
$blog = new Blog();
$form = $this-\>createForm(BlogType::class, $blog);
$form-\>handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$additionalImages = $form->get('additional_images')->getData();
if ($additionalImages) {
$originalFilename = pathinfo($additionalImages->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $additionalImages->guessExtension();
try {
$additionalImages->move(
$this->getParameter('blogimage_directory'),
$newFilename
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$blog->setAdditionalImages($newFilename);
}
}
If I add "multiple => true' to this form I get an "expected String" error on the front.
This is the form used to upload images to a blog:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('additional_images', FileType::class, [
'label' => 'Additional images',
'mapped' => false,
'multiple' => true,
'required' => false,
'constraints' => [`your text`
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
],
]);
$builder->get('additional_images')
->addModelTransformer(new CallbackTransformer(
function ($additionalAsArray) {
// transform the array to a string
return implode('; ', $additionalAsArray);
},
function ($additionalAsString) {
// transform the string back to an array
return explode('; ', $additionalAsString);
}
))
;
}
This is the blog entity class which contains the image(s)
#[ORM\Entity(repositoryClass: BlogRepository::class)]
class Blog
{
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $additional_images = null;
}
I tried adding 'multiple => true' to the form and it works, as the user is able to select multiple files. But after submitting I get "implode(): Argument #1 ($pieces) must be of type array, string given"
I found out that all I had to do was add "new All" to the form:
->add('additional_images', FileType::class, [
'label' => 'Additional images',
'mapped' => false,
'required' => false,
'multiple' => true,
'constraints' => [
new All([
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
])
],
]);
And made my controller work with an array:
$additionalImages = $form->get('additional_images')->getData();
if ($additionalImages) {
$result = array();
foreach ($additionalImages as $image)
{
$originalFilename = pathinfo($image->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $image->guessExtension();
try {
$image->move(
$this->getParameter('blogimage_directory'),
$newFilename
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$result[] = $newFilename;
}
$blog->setAdditionalImages(implode(";", $result));
}
I know that segments are not supported in the current v1Beta api (https://analyticsdata.googleapis.com/v1beta/properties/{property_id}:runReport).
Is there a workaround to get segmented data from using this api?
Reference: https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport
I was working with google analytics API GA4 using PHP hope this helps you
<?php
class analytics{
private $property_id =null;
private $client = null;
public $propertys;
public function setPropertyId($propertyid)
{
return $this->property_id = $propertyid;
}
private function getCredentials()
{
return DIR.'PATH/credentials.json');
}
private function getClient(): BetaAnalyticsDataClient
{
return $this->client= new BetaAnalyticsDataClient([
'credentials' => $this->getCredentials(),
]);
}
public function get($propertyid,$startdate,$enddate,dimensions,$metrics)
{
$response = $this->getClient()->runReport([
'property' => 'properties/' . $this->setPropertyId($propertyid),
'dateRanges' => [
new DateRange([
'start_date' => $startdate,
'end_date' => $enddate,
]),
],
'dimensions' => [new Dimension(
[
'name' => $dimensions,
]
),
],
'metrics' => [new Metric(
[
'name' => $metrics,
]
)
]
]);
foreach ($response->getRows() as $row) {
return $row->getMetricValues()[0]->getValue();
}
}
}
then you can use the get function with dates range and dimensions metrics you want
I am using wp_remote_post() to post products to a third party site. First I get these products via API from a site called Mercado Libre. But when I carry out the mass publication, the products are published without a name, without a price, and without respecting the parameters provided. They only have the name "Product".
function publish_products_in_woocommerce()
{
$respuesta = wp_remote_get('https://api.mercadolibre.com/sites/MLA/search?q=Motorola%20G6', array(
'headers' => array(
'Authorization' => 'Bearer APP_USR-2778915669536100-052221-0f3a39361bdcea9bc3de0df8ab619f66-370993848'
)
));
$respuesta = json_decode($respuesta['body'], true);
print_r($respuesta);
foreach ($respuesta['results'] as $value) {
$product_data = array(
'name' => $value['title'],
'status' => 'draft',
'type' => 'simple',
'regular_price' => $value['price'],
'description' => $value['title'],
'short_description' => $value['title'],
'categories' => [
[
'id' => $value['category_id'],
]
],
'images' => [
[
'src' => $value['thumbnail']
]
]
);
$woocommerce_api_ck = 'ck_b2a0f58d07590e8283302eca04fbc1b66a9ff653';
$woocommerce_api_cs = 'cs_b4d91662590be47416f663fc9f0d1c49f600e394';
$url = 'http://nuevo.labisbal.com.ar/wp-json/wc/v3/products';
wp_remote_post('http://nuevo.labisbal.com.ar/wp-json/wc/v3/products', array(
'headers' => array(
'Authorization ' => 'Basic ' . base64_encode($woocommerce_api_ck . ':' . $woocommerce_api_cs),
),
'body' => $ProductToWooCommerce = json_encode($product_data),
'method' => 'POST',
'timeout' => 145,
'blocking' => false,
'sslverify' => false,
'stream' => true,
'data_format' => 'json'
));
print_r($ProductToWooCommerce);
if (wp_remote_retrieve_response_message($respuesta) === 'Created') {
echo 'The product has been created';
}
}
}
publish_products_in_woocommerce();
?>
It's strange because when I display the json already converted (using json_encode) the data is displayed correctly... (using print_r)
I take a look at this and the issue is on how you sent the data to the API, when you use wp_remote_post you need to send the body as an array, and the method handles that information for you, so you don't need to convert it to JSON.
Below is a small test plugin that saves products using the WC REST API, the plugin adds a new endpoint to your site to trigger the scrapper https://yoursite.com/wp-json/wpapi/v1/fetch, I hope that helps.
Note: Please double check the code, is not fully tested.
<?php
/**
* Plugin Name: A WC API test
* Plugin URI: https://enriquechavez.co
* Description: Just a test.
* Version: 1.0.0
* Requires at least: 5.2
* Requires PHP: 7.4
* Author: Enrique Chavez
* Author URI: https://enriquechavez.co/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Update URI: https://example.com/my-plugin/
* Text Domain: my-basics-plugin
* Domain Path: /languages
*/
add_action('rest_api_init', function () {
register_rest_route('wpapi/v1', 'fetch', [
'methods' => WP_REST_Server::READABLE,
'callback' => 'fetch_ml_products',
'permissions_callback' => '__return_true',
]);
});
function fetch_ml_products(WP_REST_Request $request)
{
$ml_api = 'https://api.mercadolibre.com/sites/MLM/search?q=Motorola%20G6&limit=5';
$wc_ck = 'ck_1f2e32852403a1701c1adxxxxxxxxxxxxxxxxxxx';
$wc_cs = 'cs_6fd5feedd2a25b73eebc9xxxxxxxxxxxxxxxxxxx';
$request = wp_safe_remote_get($ml_api);
if (is_wp_error($request)) {
return $request;
}
$response = json_decode(wp_remote_retrieve_body($request));
$new_products = [];
foreach ($response->results as $product) {
$wc_api_url = 'https://wp.test/wp-json/wc/v3/products/';
$request_data = [
'headers' => [
'Authorization' => 'Basic ' . base64_encode($wc_ck . ':' . $wc_cs),
],
'body' => [
'name' => $product->title,
'status' => 'draft',
'regular_price' => $product->price,
],
'sslverify' => false,
];
$request = wp_remote_post($wc_api_url, $request_data);
if (is_wp_error($request)) {
// TODO: validate the error.
print_r($request);
die('Something is wrong');
}
$new_products[] = json_decode(wp_remote_retrieve_body($request));
}
return $new_products;
}
I'm currently stuck on a problem I can't figure out on a wordpress side project. Here's the thing :
I made a plugin that queries an API, and processes its data to insert posts.
The problem I face is that, when the main function is triggered manually (i.e. via a route or cron manually launched) posts are correctly created or updated, but when the cron executed automatically, posts are always duplicated. I may have narrowed the problem down to capabilities (I check if posts exists by querying the cpt for a unique meta-field), and when the cron executes on its own, it find no ids.
Here are code samples :
public function insertPosts() {
set_current_user(1); // <- here I tried setting the user as admin for the script, but it didn't work
// First, get all prestations from the api
$prestations = $this->api->getPrestations();
/*
* Then :
* we remove sessions of formations initiales, which are useless (gamme id 10 = formations initiales)
* I could have "only" taken the ids I need, but unsetting useless array entries allows for a lighter dataset
*/
$prestations_info = [];
foreach ($prestations as $key => $prestation) {
if ($prestation['gamme']['id'] == 10) {
// Removes the useless $prestation and breaks out of the loop so its ID's not stored
unset($prestations[$key]);
continue;
}
$prestations_info[] = [
'fid' => $prestation['id'],
'cat_name' => $prestation['gamme']['name'],
'cat_id' => $prestation['gamme']['id']
];
}
/*
* Then :
* Parse the array to process every id individually and query the api for core informations for the singles
*/
foreach ($prestations_info as $presta) {
// Meta query to check if a post with the formation's id already exists
$args = [
'post_type' => 'formations',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => 'api_fid',
'value' => $presta['fid'],
'compare' => '='
]
]
];
$posts = new WP_Query($args);
if ($posts->have_posts()) {
while ($posts->have_posts()) {
// If a post with that formation id already exists, we retrieve its ID:
// this way, we can update existing post and avoid duplicates
$posts->the_post();
$post_id = get_the_ID();
}
}
$body = $this->api->getSessionsByPrestationsId($presta['fid']);
$post_array = [];
$locations = [];
$nb_places = [];
foreach ($body as $key => $item) {
if (!empty($item['seances'])) {
$duration = BOUtilities::getFormationDuration($item['seances']);
$dates = BOUtilities::getDatesPerSessions($item['seances']);
}
if ($dates) {
$session_date = [];
if (is_array($dates)) {
foreach ($dates as $i => $date) {
$session_date[$i] = [
'dates' => $date['start'] .' - ' . $date['end'],
];
}
} else {
$session_date[] = [
'dates' => $dates,
];
}
}
if (!empty($item['location'])) {
$locations[] = strtolower($item['location']);
$session[] = [
'sessions_location' => $item['location'],
'sessions_dates' => $session_date,
];
}
if (!empty($item['nbplace']) && $item['nbplace'] != 0) {
if (!in_array($item['nbplace'], $nb_places)) {
$nb_places[] = $item['nbplace'];
}
}
if ($item['price'] != 0) {
$prices[] = $item['price'];
}
}
$formatted_cost = BOUtilities::setPrices($prices);
$effectifs = BOUtilities::setEffectifs($nb_places);
if ($post_id) {
$post_array = [
'ID' => $post_id,
'post_type' => 'formations',
'post_title' => $item['prestation']['name'],
'meta_input' => [
'api_fid' => $presta['fid'],
'aside_cost' => $formatted_cost,
'aside_duration' => ($duration > 1 ? $duration . ' jours' : $duration . ' jour'),
'aside_effectifs' => $effectifs,
]
];
$inserted_post = wp_update_post($post_array);
}
else {
$post_array = [
'post_type' => 'formations',
'post_title' => $item['prestation']['name'],
'meta_input' => [
'api_fid' => $presta['fid'],
'aside_cost' => $formatted_cost,
'aside_duration' => ($duration > 1 ? $duration . ' jours' : $duration . ' jour'),
'aside_effectifs' => $effectifs,
]
];
$inserted_post = wp_insert_post($post_array);
}
// The cron does not have the capability of adding taxonomy terms, they need to be be added separately
$inserted_terms = wp_set_object_terms($inserted_post, $presta['cat_name'], 'categories_formations');
$inserted_terms = wp_set_object_terms($inserted_post, $locations, 'ville');
update_field('aside_sessions', $session, $inserted_post);
unset($dates);
unset($session);
unset($formatted_cost);
unset($nb_places);
unset($prices);
}
}
This is very much work in progress, but, for now, I am stumped.
I'm trying to save my ManyToMany relations between users and categories. Actually I'm trying to save my category with given users, but this doesn't work.
Form
$builder->add('name')
->add('users', EntityType::class, array(
'label' => 'Benutzer',
'class' => 'AppBundle\Entity\User',
'multiple' => true,
'expanded' => true,
'required' => false,
'choice_label' => function (User $user) {
return $user->getUsername();
}
))
->add('submit', SubmitType::class, array(
'label' => 'Speichern'
));
Form Handler
public function onSuccess(Request $request)
{
// Get category from form
$category = $this->getForm()->getData();
// Redirect to parent category when setted
if ($this->parent) {
$category->setParent($this->parent);
$response = new RedirectResponse($this->router->generate('categories.view', [
'category' => $this->parent->getId()
]));
} else {
// Build new redirect response
$response = new RedirectResponse($this->router->generate('categories.view', [
'category' => $category->getId()
]));
}
try {
// Save category in database
$this->em->merge($category);
$this->em->flush();
} catch (ORMException $ex) {
throw $ex;
}
return $response;
}
Maybe you have to unserialize the Entity $category first?
$detachedCategory = unserialize($category);
$this->em->merge($detachedCategory);
$this->em->flush();
I found this link regarding that:
How to manage deserialized entities with entity manager?
Not sure if that's the answer, but you might want to do more research.