Skip to content

Commit

Permalink
Merge branch 'release/37.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
einpraegsam committed Oct 25, 2024
2 parents 847a53e + 80ab446 commit 45702da
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 22 deletions.
8 changes: 8 additions & 0 deletions Classes/Controller/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ public function resetFilterAction(string $redirectAction): ResponseInterface
return $this->redirect($redirectAction);
}

public function tableSortingAction(string $sortingField, string $sortingDirection, string $redirectAction): ResponseInterface
{
$filter = BackendUtility::getFilterArrayFromSession($redirectAction, $this->getControllerName());
$filter['sortingField'] = $sortingField;
$filter['sortingDirection'] = $sortingDirection;
return $this->redirect($redirectAction, null, null, ['filter' => $filter]);
}

/**
* @return string like "Analysis" or "Lead"
*/
Expand Down
4 changes: 2 additions & 2 deletions Classes/Domain/Factory/VisitorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,11 @@ protected function getIpAddress(): string

protected function checkIdentificator(string $identificator): void
{
$length = [0, 32, 33];
$length = [0, Fingerprint::IDENTIFICATOR_LENGTH_FINGERPRINT, Fingerprint::IDENTIFICATOR_LENGTH_STORAGE];
if (in_array(strlen($identificator), $length) === false) {
throw new IdentificatorFormatException('Identificator is in wrong format: ' . $identificator, 1680203759);
}
if (preg_replace('/[a-z0-9]{32,33}/', '', $identificator) !== '') {
if (preg_replace('/[a-z0-9]{' . Fingerprint::IDENTIFICATOR_LENGTH_FINGERPRINT . ',' . Fingerprint::IDENTIFICATOR_LENGTH_STORAGE . '}/', '', $identificator) !== '') {
throw new IdentificatorFormatException('Identificator is in wrong format: ' . $identificator, 1680204272);
}
}
Expand Down
10 changes: 8 additions & 2 deletions Classes/Domain/Model/Fingerprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use In2code\Lux\Exception\FingerprintMustNotBeEmptyException;
use In2code\Lux\Utility\BackendUtility;
use In2code\Lux\Utility\EnvironmentUtility;
use In2code\Lux\Utility\IpUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use WhichBrowser\Parser;

Expand All @@ -16,6 +17,9 @@ class Fingerprint extends AbstractModel
public const TYPE_FINGERPRINT = 0;
public const TYPE_COOKIE = 1;
public const TYPE_STORAGE = 2;
public const IDENTIFICATOR_LENGTH_FINGERPRINT = 32;
public const IDENTIFICATOR_LENGTH_STORAGE = 33;
public const IDENTIFICATOR_LENGTH_FINGERPRINTWITHIPHASH = 64;

protected string $value = '';
protected string $domain = '';
Expand Down Expand Up @@ -49,10 +53,12 @@ public function setValue(string $value): self
if ($value === '') {
throw new FingerprintMustNotBeEmptyException('Value is empty', 1585901797);
}
if (strlen($value) === 33) {
if (strlen($value) === self::IDENTIFICATOR_LENGTH_STORAGE) {
$this->setType(self::TYPE_STORAGE);
$this->value = $value;
} else {
$this->value = hash('sha256', $value . IpUtility::getIpAddress());
}
$this->value = $value;
return $this;
}

Expand Down
43 changes: 43 additions & 0 deletions Classes/Domain/Model/Transfer/FilterDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use In2code\Lux\Utility\StringUtility;
use Throwable;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;

/**
* Class FilterDto is a filter class that helps to filter visitors by given parameters. Per default, get visitors
Expand All @@ -38,6 +39,8 @@ class FilterDto
protected string $searchterm = '';
protected string $href = '';
protected string $pid = '';
protected string $sortingField = '';
protected string $sortingDirection = QueryInterface::ORDER_DESCENDING;

/**
* Must be a string like "2021-01-01T15:03:01.012345Z" (date format "c")
Expand Down Expand Up @@ -923,6 +926,46 @@ protected function getYearIntervals(): array
return $interval;
}

public function getSortingField(): string
{
return StringUtility::cleanString($this->sortingField);
}

public function isSortingFieldSet(): bool
{
return $this->getSortingField() !== '';
}

public function setSortingField(string $sortingField): self
{
$this->sortingField = $sortingField;
return $this;
}

public function getSortingDirection(): string
{
return StringUtility::cleanString($this->sortingDirection);
}

public function setSortingDirection(string $sortingDirection): self
{
$this->sortingDirection = $sortingDirection;
return $this;
}

public function getOrderings(): array
{
$orderings = [
'identified' => QueryInterface::ORDER_DESCENDING,
'scoring' => QueryInterface::ORDER_DESCENDING,
'tstamp' => QueryInterface::ORDER_DESCENDING,
];
if ($this->isSortingFieldSet()) {
$orderings = [$this->getSortingField() => $this->getSortingDirection()];
}
return $orderings;
}

/**
* Get all sites on which the current editor has reading access
*
Expand Down
6 changes: 1 addition & 5 deletions Classes/Domain/Repository/VisitorRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ public function findAllWithIdentifiedFirst(FilterDto $filter): array
$logicalAnd = $this->extendLogicalAndWithFilterConstraints($filter, $query, []);
$logicalAnd = $this->extendLogicalAndWithFilterConstraintsForSite($filter, $query, $logicalAnd, 'pagevisits');
$query->matching($query->logicalAnd(...$logicalAnd));
$query->setOrderings([
'identified' => QueryInterface::ORDER_DESCENDING,
'scoring' => QueryInterface::ORDER_DESCENDING,
'tstamp' => QueryInterface::ORDER_DESCENDING,
]);
$query->setOrderings($filter->getOrderings());
$query->setLimit($filter->getLimit());
return $query->execute()->toArray();
}
Expand Down
2 changes: 2 additions & 0 deletions Configuration/Backend/Modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
'remove',
'deactivate',
'resetFilter',
'tableSorting',
'companies',
'companiesDisabled',
'company',
Expand Down Expand Up @@ -65,6 +66,7 @@
'remove',
'deactivate',
'resetFilter',
'tableSorting',
'companies',
'companiesDisabled',
'company',
Expand Down
69 changes: 69 additions & 0 deletions Documentation/Privacy/FingerprintsAndLocalStorage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
![LUX](/Documentation/Images/logo_claim.svg#gh-light-mode-only "LUX")
![LUX](/Documentation/Images/logo_claim_white.svg#gh-dark-mode-only "LUX")

## Introduction

Whenever a lead visits your website, we want to track which pages have been seen or which document were downloaded.
This makes it necessary to recognize the lead when leaving page a and entering page b or when the lead comes again on
another day.
To fulfill this requirement, there a basically two different possible technics available with LUX.

### Fingerprint (recommended for B2B)

Per default LUX runs in fingerprint mode and try to automatically recognize a visitor by its hardware combined with
his IP-address. Those values are hashed to one string (sha256) and cannot be reverted or splitted into its originally
parts.

Values are:

* userAgent
* webdriver
* language
* colorDepth
* deviceMemory
* hardwareConcurrency
* screenResolution and availableScreenResolution
* timezone and timezoneOffset
* sessionStorage and localStorage
* indexedDB and openDatabase
* addBehavior
* cpuClass
* platform
* plugins
* canvas
* webgl and webglVendorRenderer
* hasLiedLanguages and hasLiedResolution and hasLiedOs and hasLiedBrowser
* touchSupport
* fonts
* audio
* IP-address

**Upside:** Fingerprint is calculated automatically and does not need to be stored anywhere on the device
(cookie or local storage). A tracking between different domains and page branches is possible
within the same TYPO3 instance.

**Downside:** To calculate a fingerprint every page request needs 200-400ms on top (asynchronous).
Beside that multiple visitors with same hard- and software
are recognized as only one visitor, if they are using the same IP-address.
This is especially true for iPhones of the same version and generation.

### LocalStorage (recommended for B2C)

**Upside:** If you have a lot of mobile visitors on your website (e.g. if you own a b2c shop for a younger target
group), you may want to differ between your visitors. So you could go for LocalStorage. This is comparable to a cookie.
A random string will be saved on the visitor's device, which can be done very quickly.
You can also differ between multiple mobile visitors - even with same hardware and IP-address.

**Downside:** You have to ask your visitor if you are allowed to store a random string the local storage of the device,
to identify your visitor. To meet GDPR rules, we would suggest you to set up a cookie banner.
In addition, a visitor of domain A is not automatically merged if he also visits domain B on the same TYPO3 instance
(every domain has its own local storage area. Of course if the user is identified on both domains, the profile will be
merged to only one).

This can be turned on via TypoScript constants:

```
plugin.tx_lux.settings.identificationMethod = 2
```

**Note:** Maybe you want to disable the "autoenable" functionality. See [OptIn and OptOut](OptInAndOptOut.md)
1 change: 1 addition & 0 deletions Documentation/Privacy/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rules of GDPR (General Data Protection Regulation) / DSGVO (Datenschutzgrundvero

* [Example privacy page content](PrivacyPage.md)
* [All related to the visitor ip address](IpAddresses.md)
* [Fingerprints vs. local storage (cookie)](FingerprintsAndLocalStorage.md)
* [All information about optin and optout](OptInAndOptOut.md)
* [Privacy related commands](Commands.md)
* [Do not track header in browser](DoNotTrack.md)
2 changes: 2 additions & 0 deletions Documentation/Privacy/OptInAndOptOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,5 @@ will respect this setting of course!

**Note:** While Firefox turns on the DNT by default for anonymous tabs, Chrome and Internet Explorer never turn this
setting on by default.

**Note:** Maybe you want to switch from fingerprint to local storage mode. See [OptIn and OptOut](FingerprintsAndLocalStorage.md)
2 changes: 2 additions & 0 deletions Documentation/Technical/Changelog/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

| Version | Date | State | TYPO3 | Description |
|------------|------------|----------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 37.0.0 !!! | 2024-10-25 | Feature | `11.5 + 12.4` | Use IP-address in fingerprint hash to get a more unique hash, add sorting for lead list |
| 36.5.1 | 2024-10-17 | Bugfix | `11.5 + 12.4` | Prevent sql exception in MySQL (not MariaDB) when filtering for a category in lead list |
| 36.5.0 | 2024-10-01 | Bugfix | `11.5 + 12.4` | Prevent exceptions when configuration is missing, fix typo in configuration, add mail provider to disallowed mail provider list |
| 36.4.0 | 2024-09-16 | Feature | `11.5 + 12.4` | Add a Modern.js file to add frontend JS with a basic bot detection (this file is not yet added per default in TypoScript and only for testing reasons available at the moment) |
Expand Down Expand Up @@ -302,6 +303,7 @@ Double check if you have cleared all caches after installing a new LUX version t

| Version | Situation | Upgrade instructions |
|--------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| From former versions to 37.0.0 | IP-address will be used for creating fingerprint hashes | There is nothing to do for your, just a note that old fingerprints may not be recognized any more because we added IP-address to the fingerprint hash to be more unique now |
| From former versions to 36.5.0 | Typo in extension configuration | Changed option from `enbaleExceptionLogging` to `enableExceptionLogging` if you turned on exception logging, you have to update your configuration |
| From former versions to 35.0.0 | Multiclient usage on some records need some time to work | Nothing to do for you - just a note: Some records have to be extended with site information, but this can only be done with new records - not with existing (affected tables: Logs, Search, Fingerprint and some boxes on Lead Dashboard). To ensure that privacy is guaranteed, you need to remove all existing LUX data in a multiclient environment. |
| From former versions to 35.0.0 | Define storage pid for editors (linklistener, shortener, utmgenerator) | Records of different tables (tx_lux_domain_model_linklistener, tx_luxenterprise_domain_model_shortener, tx_luxenterprise_domain_model_utmgenerator_uri) can be stored on different pids depending on the backend user - see [Editors configuration](../Editors/Index.md) for details. |
Expand Down
22 changes: 12 additions & 10 deletions Documentation/Technical/Installation/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,29 +161,31 @@ localstorage mode. While a fingerprint can be calculated by hardware details aut
similar to a cookie. We would also don't use `autoenable` if you want to go for local storage and ask your visitors
for an opt-in.

###### Fingerprint for b2b
###### Fingerprint (recommended for B2B)

**Upside:** Fingerprint is calculated automatically and does not need to be stored anywhere on the device
(cookie or local storage). A tracking between different domains and page branches is possible
within the same TYPO3 instance.

**Downside:** To calculate a fingerprint 200-400ms is needed. Beside that multiple visitors with same hard- and software
are recognized as only one visitor. This is especially true for iPhones of the same version and generation.
**Downside:** To calculate a fingerprint every page request needs 200-400ms on top (asynchronous).
Beside that multiple visitors with same hard- and software
are recognized as only one visitor, if they are using the same IP-address.
This is especially true for iPhones of the same version and generation.

###### LocalStorage for b2c
###### LocalStorage (recommended for B2C)

**Upside:** If you have a lot of iPhone visitors on your website (e.g. if you own a b2c shop for a younger target
group), you may want to differ between your visitors. So you could go for LocalStorage.
A random string is generated where fast and saved on the visitors device. You can also differ between multiple iPhone
visitors.
**Upside:** If you have a lot of mobile visitors on your website (e.g. if you own a b2c shop for a younger target
group), you may want to differ between your visitors. So you could go for LocalStorage. This is comparable to a cookie.
A random string will be saved on the visitor's device, which can be done very quickly.
You can also differ between multiple mobile visitors - even with same hardware and IP-address.

**Downside:** You have to ask your visitor if you are allowed to store a random string the local storage of the device,
to identify your visitor. To meet GDPR rules, we would suggest you to set up a cookie banner.
In addition a visitor of domain A is not automatically merged if he also visits domain B on the same TYPO3 instance
In addition, a visitor of domain A is not automatically merged if he also visits domain B on the same TYPO3 instance
(every domain has its own local storage area. Of course if the user is identified on both domains, the profile will be
merged to only one).

####
#### TypoScript

If you want to see in detail what kind of TypoScript will be included and how to overwrite some parts, look at
[the Lux folder](../../../Configuration/TypoScript/Lux)
Expand Down
2 changes: 2 additions & 0 deletions Resources/Private/Partials/Filter/Lead/List.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ <h5>Scoring</h5>
</div>
</div>
</div>
<f:form.hidden property="sortingField" />
<f:form.hidden property="sortingDirection" />
</f:form>
</div>
</div>
28 changes: 28 additions & 0 deletions Resources/Private/Partials/Filter/TableSorting.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<f:if condition="{filter.sortingField} == {sortingField}">
<f:then>
<f:if condition="{filter.sortingDirection} == 'DESC'">
<f:then>
<f:link.action action="tableSorting" arguments="{sortingField:sortingField,sortingDirection:'ASC',redirectAction:view.action}">
<f:form.button class="btn btn-link">
<core:icon identifier="actions-sort-amount-down"></core:icon>
</f:form.button>
</f:link.action>
</f:then>
<f:else>
<f:link.action action="tableSorting" arguments="{sortingField:sortingField,sortingDirection:'DESC',redirectAction:view.action}">
<f:form.button class="btn btn-link">
<core:icon identifier="actions-sort-amount-up"></core:icon>
</f:form.button>
</f:link.action>
</f:else>
</f:if>
</f:then>
<f:else>
<f:link.action action="tableSorting" arguments="{sortingField:sortingField,sortingDirection:'DESC',redirectAction:view.action}">
<f:form.button class="btn btn-link">
<core:icon identifier="actions-sort-amount"></core:icon>
</f:form.button>
</f:link.action>
</f:else>
</f:if>

Loading

0 comments on commit 45702da

Please sign in to comment.