Skip to content

Commit

Permalink
Merge pull request #46 from AlexisPPLIN/feature/consent-mode
Browse files Browse the repository at this point in the history
Add support for consent mode
  • Loading branch information
br33f authored Apr 20, 2024
2 parents 2ea7fbf + 8ffd2ca commit 0bd65e2
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 5 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,40 @@ $baseRequest = new BaseRequest();
$baseRequest->setAppInstanceId('APP_INSTANCE_ID'); // instead of setClientId(...)
```

### Consent mode v2

This library supports consent mode v2 : [see documentation](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=firebase#payload_consent)

```php
use Br33f\Ga4\MeasurementProtocol\Dto\Request\BaseRequest;
use Br33f\Ga4\MeasurementProtocol\Dto\Common\ConsentProperty;
use Br33f\Ga4\MeasurementProtocol\Enum\ConsentCode;

// Create consent property with :
// - ad_user_data = GRANTED
// - ad_personalization = DENIED
$consent = new ConsentProperty();
$consent->setAdUserData(ConsentCode::GRANTED);
$consent->setAdPersonalization(ConsentCode::DENIED);

// Create base request
$baseRequest = new BaseRequest();
$baseRequest->setConsent($consent);
```

This mode replaces now obsolete / deprecated `non_personalized_ads` :

```php
$baseRequest = new BaseRequest();
$baseRequest->setNonPersonalizedAds(true);

// Is replaced by :

$consent = new ConsentProperty();
$consent->setAdPersonalization(ConsentCode::GRANTED);
$baseRequest->setConsent($consent);
```


## Debug event data and requests
Debuging event data is possible by sending them to debug endpoint (Measurement Protocol Validation Server), since default endpoint for Google Analytics 4 Measurement Protocol does not return any HTTP error codes or messages. In order to validate event one should use `sendDebug($request)` method instead of `send($request)`.
Expand Down
89 changes: 89 additions & 0 deletions src/Dto/Common/ConsentProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/**
* User: Alexis POUPELIN (AlexisPPLIN)
* Date: 08.04.2024
* Time: 11:00
*/

namespace Br33f\Ga4\MeasurementProtocol\Dto\Common;

use Br33f\Ga4\MeasurementProtocol\Dto\ExportableInterface;
use Br33f\Ga4\MeasurementProtocol\Enum\ConsentCode;

class ConsentProperty implements ExportableInterface
{
/**
* Sets consent for sending user data from the request's events and user properties to Google for advertising purposes.
* Must be either {@see ConsentCode::GRANTED} or {@see ConsentCode::DENIED}
* @var string
*/
protected $ad_user_data;

/**
* Sets consent for personalized advertising for the user.
* Must be either {@see ConsentCode::GRANTED} or {@see ConsentCode::DENIED}
* @var string
*/
protected $ad_personalization;

/**
* ConsentProperty constructor
* Each parameters must be either {@see ConsentCode::GRANTED} or {@see ConsentCode::DENIED}
* @param string|null $ad_user_data
* @param string|null $ad_personalization
*/
public function __construct(?string $ad_user_data = null, ?string $ad_personalization = null)
{
$this->ad_user_data = $ad_user_data;
$this->ad_personalization = $ad_personalization;
}

public function export() : array
{
$result = [];

if (isset($this->ad_user_data)) {
$result['ad_user_data'] = $this->ad_user_data;
}

if (isset($this->ad_personalization)) {
$result['ad_personalization'] = $this->ad_personalization;
}

return $result;
}

/**
* @return string|null
*/
public function getAdUserData() : ?string
{
return $this->ad_user_data;
}

/**
* Must be either {@see ConsentCode::GRANTED} or {@see ConsentCode::DENIED}
* @param string|null $ad_user_data
*/
public function setAdUserData(?string $ad_user_data) : void
{
$this->ad_user_data = $ad_user_data;
}

/**
* @return string|null
*/
public function getAdPersonalization() : ?string
{
return $this->ad_personalization;
}

/**
* Must be either {@see ConsentCode::GRANTED} or {@see ConsentCode::DENIED}
* @param string|null $ad_personalization
*/
public function setAdPersonalization(?string $ad_personalization) : void
{
$this->ad_personalization = $ad_personalization;
}
}
54 changes: 50 additions & 4 deletions src/Dto/Request/BaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Br33f\Ga4\MeasurementProtocol\Dto\Common\UserDataItem;
use Br33f\Ga4\MeasurementProtocol\Dto\Common\UserProperties;
use Br33f\Ga4\MeasurementProtocol\Dto\Common\UserProperty;
use Br33f\Ga4\MeasurementProtocol\Dto\Common\ConsentProperty;
use Br33f\Ga4\MeasurementProtocol\Dto\Event\AbstractEvent;
use Br33f\Ga4\MeasurementProtocol\Enum\ErrorCode;
use Br33f\Ga4\MeasurementProtocol\Exception\ValidationException;
Expand Down Expand Up @@ -61,11 +62,18 @@ class BaseRequest extends AbstractRequest

/**
* If set true - indicates that events should not be use for personalized ads.
* Default false
* @var bool
* Not required
* @var ?bool
*/
protected $nonPersonalizedAds = false;
protected $nonPersonalizedAds = null;

/**
* Sets the consent settings for the request.
* Replaces non_personalized_ads
* Not required
* @var ConsentProperty
*/
protected $consent = null;

/**
* Collection of event items. Maximum 25 events.
Expand Down Expand Up @@ -129,6 +137,24 @@ public function setUserProperties(?UserProperties $userProperties)
return $this;
}

/**
* @return ConsentProperty|null
*/
public function getConsent() : ?ConsentProperty
{
return $this->consent;
}

/**
* @param ConsentProperty|null $consent
* @return BaseRequest
*/
public function setConsent(?ConsentProperty $consent) : self
{
$this->consent = $consent;
return $this;
}


/**
* @param UserData $userProperty
Expand Down Expand Up @@ -198,10 +224,13 @@ public function export(): array
$exportBaseRequest = array_filter([
'client_id' => $this->getClientId(),
'app_instance_id' => $this->getAppInstanceId(),
'non_personalized_ads' => $this->isNonPersonalizedAds(),
'events' => $this->getEvents()->export(),
]);

if ($this->getNonPersonalizedAds() !== null) {
$exportBaseRequest['non_personalized_ads'] = $this->isNonPersonalizedAds();
}

if ($this->getUserId() !== null) {
$exportBaseRequest['user_id'] = $this->getUserId();
}
Expand All @@ -218,6 +247,10 @@ public function export(): array
$exportBaseRequest['user_data'] = $this->getUserData()->export();
}

if ($this->getConsent() !== null) {
$exportBaseRequest['consent'] = $this->getConsent()->export();
}

return $exportBaseRequest;
}

Expand Down Expand Up @@ -261,6 +294,19 @@ public function setAppInstanceId(string $appInstanceId): self
* @return bool
*/
public function isNonPersonalizedAds(): bool
{
$nonPersonalizedAds = $this->getNonPersonalizedAds();
if (!isset($nonPersonalizedAds)) {
return false;
}

return $this->nonPersonalizedAds;
}

/**
* @return ?bool
*/
public function getNonPersonalizedAds() : ?bool
{
return $this->nonPersonalizedAds;
}
Expand Down
15 changes: 15 additions & 0 deletions src/Enum/ConsentCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* User: Alexis POUPELIN (AlexisPPLIN)
* Date: 08.04.2024
* Time: 11:00
*/

namespace Br33f\Ga4\MeasurementProtocol\Enum;


class ConsentCode
{
const GRANTED = 'GRANTED';
const DENIED = 'DENIED';
}
85 changes: 85 additions & 0 deletions tests/Ga4/MeasurementProtocol/Dto/Common/ConsentPropertyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
/**
* User: Alexis POUPELIN (AlexisPPLIN)
* Date: 08.04.2024
* Time: 11:00
*/

namespace Tests\Ga4\MeasurementProtocol\Dto\Common;

use Br33f\Ga4\MeasurementProtocol\Dto\Common\ConsentProperty;
use Br33f\Ga4\MeasurementProtocol\Enum\ConsentCode;
use Tests\Common\BaseTestCase;

class ConsentPropertyTest extends BaseTestCase
{
/**
* @var ConsentProperty
*/
protected $consentProperty;

public function testDefaultConstructor()
{
$constructedConsentProperty = new ConsentProperty();

$this->assertNull($constructedConsentProperty->getAdUserData());
$this->assertNull($constructedConsentProperty->getAdPersonalization());
}

public function testParametrizedConstructor()
{
$ad_user_data = ConsentCode::DENIED;
$ad_personalization = ConsentCode::GRANTED;
$constructedConsentProperty = new ConsentProperty($ad_user_data, $ad_personalization);

$this->assertEquals($ad_user_data, $constructedConsentProperty->getAdUserData());
$this->assertEquals($ad_personalization, $constructedConsentProperty->getAdPersonalization());
}

public function testAdUserData()
{
$ad_user_data = ConsentCode::DENIED;
$this->consentProperty->setAdUserData($ad_user_data);

$this->assertEquals($ad_user_data, $this->consentProperty->getAdUserData());
}

public function testAdPersonalization()
{
$ad_personalization = ConsentCode::GRANTED;
$this->consentProperty->setAdPersonalization($ad_personalization);

$this->assertEquals($ad_personalization, $this->consentProperty->getAdPersonalization());
}

public function testExportEmpty()
{
$emptyConsentProperty = new ConsentProperty();

$this->assertEquals([], $emptyConsentProperty->export());
}

public function testPartialExport()
{
$ad_user_data = ConsentCode::DENIED;
$ad_personalization = null;
$constructedConsentProperty = new ConsentProperty($ad_user_data, $ad_personalization);

$this->assertEquals(['ad_user_data' => 'DENIED'], $constructedConsentProperty->export());
}

public function testExport()
{
$ad_user_data = ConsentCode::DENIED;
$ad_personalization = ConsentCode::GRANTED;
$constructedConsentProperty = new ConsentProperty($ad_user_data, $ad_personalization);

$this->assertEquals(['ad_user_data' => 'DENIED', 'ad_personalization' => 'GRANTED'], $constructedConsentProperty->export());
}

protected function setUp(): void
{
parent::setUp();
$this->consentProperty = new ConsentProperty();
}
}
Loading

0 comments on commit 0bd65e2

Please sign in to comment.