Skip to content

Commit 7e3aadb

Browse files
committed
Merge branch 'master' of github.com:aawnu/php-ga4
2 parents 36871d0 + f227738 commit 7e3aadb

File tree

9 files changed

+105
-62
lines changed

9 files changed

+105
-62
lines changed

.github/workflows/cron.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Cron | Monthly Health Check
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: 0 2 1 * *
7+
8+
jobs:
9+
validate-master:
10+
if: github.ref == 'refs/heads/master'
11+
uses: ./.github/workflows/ci.yml

README.md

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,26 @@ $analytics = Analytics::new(
6565
api_secret: 'xYzzX_xYzzXzxyZxX',
6666
debug: true|false
6767
);
68+
69+
// You can set CONSENT here if not done through the gtat.js
70+
// Read full docs here: https://support.google.com/tagmanager/answer/13802165
71+
$consent = $analytics->consent(); // returns a consent handler
72+
73+
// Sets consent for sending user data from the request's events
74+
// and user properties to Google for advertising purposes.
75+
$consent->setAdUserDataPermission();
76+
$consent->getAdUserDataPermission();
77+
$consent->clearAdUserDataPermission();
78+
79+
// Sets consent for personalized advertising for the user.
80+
$consent->setAdPersonalizationPermission();
81+
$consent->getAdPersonalizationPermission();
82+
$consent->clearAdPersonalizationPermission();
6883
```
6984

7085
### Data flow
7186

72-
`session_id` > Google Analytics does not specify a required type of **session or user id**. You are free to use any kind of **unique identifier** you want; the catch, however, is that Google Analytics populates some internal data with `gtag.js`, that is then referenced to their `_ga` cookie session id. Just be aware that `gtag.js` is using *client-side Javascript* and can therefore have some **GDPR complications** as requests back to Google Analytics contains client information; such as their IP Address.
87+
`session_id` > Google Analytics does not specify a required type of **session or user id**. You are free to use any kind of **unique identifier** you want; the catch, however, is that Google Analytics populates some internal data with `gtag.js`, that is then referenced to their `_ga` cookie session id. Just be aware that `gtag.js` is using _client-side Javascript_ and can therefore have some **GDPR complications** as requests back to Google Analytics contains client information; such as their IP Address.
7388

7489
1. Acquire proper GDPR Consent
7590
2. Client/GTAG.js sends session_start and first_visit to GA4
@@ -143,7 +158,7 @@ $event->setEventPage($eventPage);
143158
![badge](https://shields.io/badge/AddShippingInfo-informational)
144159
![badge](https://shields.io/badge/Purchase-informational)
145160
![badge](https://shields.io/badge/Refund-informational)
146-
161+
147162
### Engagement / Gaming
148163

149164
![badge](https://shields.io/badge/EarnVirtualCurrency-informational)
@@ -175,12 +190,12 @@ foreach ($visitors as $collection) {
175190
// Group of events, perhaps need logic to change from json or array to event objects
176191
// Maybe its formatted well for the > ConvertHelper::parseEvents([...]) < helper
177192
$groups = $collection['events'];
178-
193+
179194
// If gtag.js, this can be the _ga or _gid cookie
180195
// This can be any kind of session identifier
181196
// Usually derives from $_COOKIE['_ga'] or $_COOKIE['_gid'] set by GTAG.js
182197
$visitor = $collection['session_id'];
183-
198+
184199
// load logged in user/visitor
185200
// This can be any kind of unique identifier, readable is easier for you
186201
// Just be wary not to use GDPR sensitive information
@@ -192,14 +207,14 @@ foreach ($visitors as $collection) {
192207
$analytics = Analytics::new($measurementId, $apiSecret)
193208
->setClientId($visitor)
194209
->setTimestampMicros($time);
195-
210+
196211
if ($user !== null) {
197212
$analytics->setUserId($user);
198213
}
199-
214+
200215
$analytics->addUserParameter(...$data['userParameters']); // pseudo logic for adding user parameters
201216
$analytics->addEvent(...$data['events']); // pseudo logic for adding events
202-
217+
203218
$analytics->post(); // send events to Google Analytics
204219
} catch (Exception\Ga4Exception $exception) {
205220
// Handle exception
@@ -216,27 +231,24 @@ foreach ($visitors as $collection) {
216231

217232
```js
218233
// array< array< eventName, array<eventParams> > >
219-
axios.post(
220-
'/your-api-endpoint/ga4-event-receiver',
221-
[
222-
// Note each event is its own object inside an array as
223-
// this allows to pass the same event type multiple times
234+
axios.post("/your-api-endpoint/ga4-event-receiver", [
235+
// Note each event is its own object inside an array as
236+
// this allows to pass the same event type multiple times
237+
{
238+
addToCart: {
239+
currency: "EUR",
240+
value: 13.37,
241+
items: [
224242
{
225-
addToCart: {
226-
currency: 'EUR',
227-
value: 13.37,
228-
items: [
229-
{
230-
'item_id': 1,
231-
'item_name': 'Cup',
232-
'price': 13.37,
233-
'quantity': 1
234-
}
235-
]
236-
}
237-
}
238-
]
239-
)
243+
item_id: 1,
244+
item_name: "Cup",
245+
price: 13.37,
246+
quantity: 1,
247+
},
248+
],
249+
},
250+
},
251+
]);
240252
```
241253

242254
#### Backend
@@ -274,7 +286,7 @@ class ExampleEvent extends AlexWestergaard\PhpGa4\Helper\EventHelper
274286
// variables should be nullable as unset() will set variable as null
275287
protected null|mixed $my_variable;
276288
protected null|mixed $my_required_variable;
277-
289+
278290
// Arrays should always be instanciated empty
279291
protected array $my_array = [];
280292

src/Analytics.php

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ public function getRequiredParams(): array
6161
return $return;
6262
}
6363

64-
public function setNonPersonalizedAds(bool $exclude)
65-
{
66-
$this->non_personalized_ads = $exclude;
67-
return $this;
68-
}
69-
7064
public function setClientId(string $id)
7165
{
7266
$this->client_id = $id;
@@ -112,7 +106,7 @@ public function addEvent(Facade\Type\EventType ...$events)
112106
return $this;
113107
}
114108

115-
public function consent()
109+
public function consent(): ConsentHelper
116110
{
117111
return $this->consent;
118112
}
@@ -130,30 +124,23 @@ public function post(): void
130124
$url = $this->debug ? Facade\Type\AnalyticsType::URL_DEBUG : Facade\Type\AnalyticsType::URL_LIVE;
131125
$url .= '?' . http_build_query(['measurement_id' => $this->measurement_id, 'api_secret' => $this->api_secret]);
132126

133-
$body = $this->toArray();
134-
array_merge_recursive(
127+
$body = array_replace_recursive(
135128
$this->toArray(),
129+
["user_properties" => $this->user_properties],
136130
["consent" => $this->consent->toArray()],
137131
);
138132

139-
$chunkUserProperties = array_chunk($this->user_properties, 25, true);
140-
$this->user_properties = [];
141-
142133
$chunkEvents = array_chunk($this->events, 25);
143-
$this->events = [];
144134

145-
$chunkMax = count($chunkEvents) > count($chunkUserProperties) ? count($chunkEvents) : count($chunkUserProperties);
135+
if (count($chunkEvents) < 1) {
136+
throw Ga4Exception::throwMissingEvents();
137+
}
146138

147-
for ($chunk = 0; $chunk < $chunkMax; $chunk++) {
148-
$body['user_properties'] = $chunkUserProperties[$chunk] ?? [];
149-
if (empty($body['user_properties'])) {
150-
unset($body['user_properties']);
151-
}
139+
$this->user_properties = [];
140+
$this->events = [];
152141

153-
$body['events'] = $chunkEvents[$chunk] ?? [];
154-
if (empty($body['events'])) {
155-
unset($body['events']);
156-
}
142+
foreach ($chunkEvents as $events) {
143+
$body['events'] = $events;
157144

158145
$kB = 1024;
159146
if (($size = mb_strlen(json_encode($body))) > ($kB * 130)) {
@@ -204,13 +191,20 @@ public static function new(string $measurementId, string $apiSecret, bool $debug
204191
* Deprecated references
205192
*/
206193

207-
/** @deprecated 1.1.1 */
194+
/** @deprecated 1.1.9 Please use `Analytics->consent->setAdPersonalizationPermission()` instead */
195+
public function setNonPersonalizedAds(bool $exclude)
196+
{
197+
$this->consent->setAdPersonalizationPermission(!$exclude);
198+
return $this;
199+
}
200+
201+
/** @deprecated 1.1.1 Please use `Analytics->consent->setAdPersonalizationPermission()` instead */
208202
public function allowPersonalisedAds(bool $allow)
209203
{
210-
$this->setNonPersonalizedAds(!$allow);
204+
$this->consent->setAdPersonalizationPermission($allow);
211205
}
212206

213-
/** @deprecated 1.1.1 */
207+
/** @deprecated 1.1.1 Please use `Analytics->setTimestampMicros()` instead */
214208
public function setTimestamp(int|float $microOrUnix)
215209
{
216210
$this->setTimestampMicros($microOrUnix);

src/Exception/Ga4Exception.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,9 @@ public static function throwRequestInvalidBody(array $msg)
8181
static::REQUEST_INVALID_BODY
8282
);
8383
}
84+
85+
public static function throwMissingEvents()
86+
{
87+
return new static("Request must include at least 1 event with a name", static::REQUEST_EMPTY_EVENTLIST);
88+
}
8489
}

src/Facade/Type/AnalyticsType.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ public function setTimestampMicros(int|float $microOrUnix);
4343
public function setNonPersonalizedAds(bool $allow);
4444

4545
/**
46-
* The user properties for the measurement
46+
* The user properties for the measurement (Up to 25 custom per project, see link)
4747
*
4848
* @var user_properties
4949
* @param AlexWestergaard\PhpGa4\Facade\Type\UserProperty $prop
50+
* @link https://support.google.com/analytics/answer/14240153
5051
*/
5152
public function addUserProperty(UserPropertyType ...$props);
5253

src/Facade/Type/Ga4ExceptionType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ interface Ga4ExceptionType
2424
const REQUEST_INVALID_BODY = 104005;
2525
const REQUEST_MISSING_MEASUREMENT_ID = 104006;
2626
const REQUEST_MISSING_API_SECRET = 104007;
27+
const REQUEST_EMPTY_EVENTLIST = 104008;
2728
}

src/Helper/ConsentHelper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
class ConsentHelper
66
{
7-
const GRANTED = "granted";
8-
const DENIED = "denied";
7+
const GRANTED = "GRANTED";
8+
const DENIED = "DENIED";
99

1010
private ?string $ad_user_data = null;
1111
private ?string $ad_personalization = null;

test/Unit/AnalyticsTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use AlexWestergaard\PhpGa4\Facade;
77
use AlexWestergaard\PhpGa4\Event;
88
use AlexWestergaard\PhpGa4\Analytics;
9+
use AlexWestergaard\PhpGa4\Event\Login;
910
use AlexWestergaard\PhpGa4Test\TestCase;
1011

1112
final class AnalyticsTest extends TestCase
@@ -17,7 +18,6 @@ public function test_can_configure_and_export()
1718
$this->prefill['api_secret'],
1819
$debug = true
1920
)
20-
->setNonPersonalizedAds($nonPersonalisedAds = true)
2121
->setClientId($this->prefill['client_id'])
2222
->setUserId($this->prefill['user_id'])
2323
->setTimestampMicros($time = time())
@@ -27,7 +27,6 @@ public function test_can_configure_and_export()
2727
$asArray = $analytics->toArray();
2828
$this->assertIsArray($asArray);
2929

30-
$this->assertArrayHasKey('non_personalized_ads', $asArray);
3130
$this->assertArrayHasKey('timestamp_micros', $asArray);
3231
$this->assertArrayHasKey('client_id', $asArray);
3332
$this->assertArrayHasKey('user_id', $asArray);
@@ -36,7 +35,6 @@ public function test_can_configure_and_export()
3635

3736
$timeAsMicro = $time * 1_000_000;
3837

39-
$this->assertEquals($nonPersonalisedAds, $asArray['non_personalized_ads']);
4038
$this->assertEquals($timeAsMicro, $asArray['timestamp_micros']);
4139
$this->assertEquals($this->prefill['client_id'], $asArray['client_id']);
4240
$this->assertEquals($this->prefill['user_id'], $asArray['user_id']);
@@ -46,7 +44,7 @@ public function test_can_configure_and_export()
4644

4745
public function test_can_post_to_google()
4846
{
49-
$this->assertNull($this->analytics->post());
47+
$this->assertNull($this->analytics->addEvent(Login::new())->post());
5048
}
5149

5250
public function test_converts_to_full_microtime_stamp()
@@ -68,6 +66,8 @@ public function test_throws_if_microtime_older_than_three_days()
6866

6967
public function test_exports_userproperty_to_array()
7068
{
69+
$this->analytics->addEvent(Login::new());
70+
7171
$userProperty = UserProperty::new()
7272
->setName('customer_tier')
7373
->setValue('premium');
@@ -138,7 +138,7 @@ public function test_throws_on_too_large_request_package()
138138
$userProperty->setValue($overflowValue);
139139
}
140140

141-
$this->analytics->addUserProperty($userProperty)->post();
141+
$this->analytics->addEvent(Login::new())->addUserProperty($userProperty)->post();
142142
}
143143

144144
public function test_timeasmicro_throws_exceeding_max()

0 commit comments

Comments
 (0)