Skip to content

Commit 2c48c7f

Browse files
committed
Merge branch 'release/44.3.0'
2 parents b20d243 + c449c02 commit 2c48c7f

7 files changed

Lines changed: 171 additions & 5 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace In2code\Lux\Command;
5+
6+
use Doctrine\DBAL\Exception as ExceptionDbal;
7+
use In2code\Lux\Domain\Model\Company;
8+
use In2code\Lux\Domain\Repository\CompanyRepository;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use TYPO3\CMS\Core\Utility\GeneralUtility;
15+
use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException;
16+
17+
class LuxCleanupCompaniesByPropertyCommand extends Command
18+
{
19+
use ExtbaseCommandTrait;
20+
21+
public function configure()
22+
{
23+
$description = 'Remove companies by a given property. E.g. removing all companies of a country';
24+
$this->setDescription($description);
25+
$this->addArgument('propertyName', InputArgument::REQUIRED, 'any property name');
26+
$this->addArgument('propertyValue', InputArgument::REQUIRED, 'any property value');
27+
$this->addArgument('exactMatch', InputArgument::OPTIONAL, 'direct match');
28+
$this->addOption('remove-visitors', null, InputOption::VALUE_NONE, 'also remove all related visitors');
29+
}
30+
31+
/**
32+
* Remove companies by a given property. E.g. removing all chinese companies with
33+
* "./vendor/bin/typo3 lux:cleanupCompaniesByProperty countryCode cn 1" or
34+
* "./vendor/bin/typo3 lux:cleanupCompaniesByProperty title "Testfirma" 0 --remove-visitors"
35+
*
36+
* !!! Really removes companies from the database
37+
* Relations to a removed company are always resolved: with --remove-visitors all related visitors and
38+
* all rows from their related tables are removed, without it the visitors are kept and only lose their
39+
* relation to the company
40+
*
41+
* @param InputInterface $input
42+
* @param OutputInterface $output
43+
* @return int
44+
* @throws InvalidQueryException
45+
* @throws ExceptionDbal
46+
*/
47+
public function execute(InputInterface $input, OutputInterface $output): int
48+
{
49+
$this->initializeExtbase();
50+
$companyRepository = GeneralUtility::makeInstance(CompanyRepository::class);
51+
$companies = $companyRepository->findAllByProperty(
52+
(string)$input->getArgument('propertyName'),
53+
(string)$input->getArgument('propertyValue'),
54+
(bool)$input->getArgument('exactMatch')
55+
);
56+
$removeVisitors = (bool)$input->getOption('remove-visitors');
57+
/** @var Company $company */
58+
foreach ($companies as $company) {
59+
$companyRepository->removeCompany($company, $removeVisitors);
60+
}
61+
$output->writeln(count($companies) . ' successfully removed');
62+
return self::SUCCESS;
63+
}
64+
}

Classes/Domain/Repository/CompanyRepository.php

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
use In2code\Lux\Domain\Model\Visitor;
1313
use In2code\Lux\Domain\Service\BranchService;
1414
use In2code\Lux\Domain\Service\CountryService;
15+
use In2code\Lux\Utility\ArrayUtility;
1516
use In2code\Lux\Utility\DatabaseUtility;
1617
use In2code\Lux\Utility\DateUtility;
18+
use In2code\Lux\Utility\StringUtility;
1719
use TYPO3\CMS\Core\Utility\GeneralUtility;
20+
use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException;
1821
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
22+
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
1923

2024
class CompanyRepository extends AbstractRepository
2125
{
@@ -83,6 +87,36 @@ public function findAmountByFilter(FilterDto $filter): int
8387
return (int)$connection->executeQuery($sql)->fetchOne();
8488
}
8589

90+
/**
91+
* Find all companies by a given property. Property paths are allowed (e.g. "category.title")
92+
*
93+
* @param string $propertyName
94+
* @param string $propertyValue
95+
* @param bool $exactMatch
96+
* @param array $orderings
97+
* @param int $limit
98+
* @return QueryResultInterface
99+
* @throws InvalidQueryException
100+
*/
101+
public function findAllByProperty(
102+
string $propertyName,
103+
string $propertyValue,
104+
bool $exactMatch,
105+
array $orderings = ['uid' => 'DESC'],
106+
int $limit = 1000
107+
): QueryResultInterface {
108+
$query = $this->createQuery();
109+
$propertyName = StringUtility::cleanString($propertyName, false, '._-');
110+
$constraint = $query->equals($propertyName, $propertyValue);
111+
if ($exactMatch === false) {
112+
$constraint = $query->like($propertyName, '%' . $propertyValue . '%');
113+
}
114+
$query->matching($constraint);
115+
$query->setOrderings(ArrayUtility::cleanStringForArrayKeys($orderings));
116+
$query->setLimit($limit);
117+
return $query->execute();
118+
}
119+
86120
public function findByTitleAndDomain(string $title, string $domain): ?Company
87121
{
88122
$query = $this->createQuery();
@@ -277,12 +311,16 @@ public function canCompanyBeReadBySites(Company $company, array $sites): bool
277311
*/
278312
public function removeCompany(Company $company, bool $removeVisitors): void
279313
{
280-
if ($removeVisitors) {
281-
$visitorRepository = GeneralUtility::makeInstance(VisitorRepository::class);
282-
foreach ($company->getVisitors() as $visitor) {
283-
$visitorRepository->removeVisitor($visitor);
314+
$visitorRepository = GeneralUtility::makeInstance(VisitorRepository::class);
315+
if ($removeVisitors === true) {
316+
foreach ($visitorRepository->findAllUidsByCompany($company) as $visitorUid) {
317+
$visitor = $visitorRepository->findByUid($visitorUid);
318+
if ($visitor !== null) {
319+
$visitorRepository->removeVisitor($visitor);
320+
}
284321
}
285322
}
323+
$visitorRepository->removeCompanyRelation($company);
286324

287325
$connection = DatabaseUtility::getConnectionForTable(Company::TABLE_NAME);
288326
$connection->executeQuery('delete from ' . Company::TABLE_NAME . ' where uid=' . (int)$company->getUid());

Classes/Domain/Repository/VisitorRepository.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,19 @@ public function getScoringSumFromCompany(Company $company): int
552552
return (int)$connection->executeQuery($sql)->fetchOne();
553553
}
554554

555+
/**
556+
* @param Company $company
557+
* @return int[]
558+
* @throws ExceptionDbal
559+
*/
560+
public function findAllUidsByCompany(Company $company): array
561+
{
562+
$connection = DatabaseUtility::getConnectionForTable(Visitor::TABLE_NAME);
563+
$sql = 'select uid from ' . Visitor::TABLE_NAME
564+
. ' where companyrecord = ' . (int)$company->getUid();
565+
return array_map('intval', $connection->executeQuery($sql)->fetchFirstColumn());
566+
}
567+
555568
public function findByCompany(Company $company, int $limit = 200): array
556569
{
557570
$connection = DatabaseUtility::getConnectionForTable(Visitor::TABLE_NAME);
@@ -624,6 +637,20 @@ public function updateRecordsWithLanguageAll(): void
624637
}
625638
}
626639

640+
/**
641+
* @param Company $company
642+
* @return void
643+
* @throws ExceptionDbal
644+
*/
645+
public function removeCompanyRelation(Company $company): void
646+
{
647+
$connection = DatabaseUtility::getConnectionForTable(Visitor::TABLE_NAME);
648+
$connection->executeQuery(
649+
'update ' . Visitor::TABLE_NAME . ' set companyrecord=0'
650+
. ' where companyrecord = ' . (int)$company->getUid()
651+
);
652+
}
653+
627654
/**
628655
* @param Visitor $visitor
629656
* @return void

Configuration/Services.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ services:
4848
- name: 'console.command'
4949
command: 'lux:cleanupAllVisitors'
5050

51+
In2code\Lux\Command\LuxCleanupCompaniesByPropertyCommand:
52+
tags:
53+
- name: 'console.command'
54+
command: 'lux:cleanupCompaniesByProperty'
55+
5156
In2code\Lux\Command\LuxCleanupUnknownVisitorsByAgeCommand:
5257
tags:
5358
- name: 'console.command'

Documentation/Technical/Changelog/Index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
| Version | Date | State | TYPO3 | Description |
1111
|------------|------------|----------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
12+
| 44.3.0 | 2026-09-02 | Feature | `v13 + v14` | Add new command to remove company records (with or without visitor records) |
1213
| 44.2.0 | 2026-09-02 | Task | `v13 + v14` | Allow deleting of visitors via command by chained properties, Show if leadfeeder requests are failing, update token pattern for leadfeeder API keys |
1314
| 44.1.1 | 2026-08-24 | Bugfix | `v13 + v14` | Allow extending of HEADER_ROBOTS for other extensions |
1415
| 44.1.0 | 2026-08-24 | Task | `v13 + v14` | Return status code 400 for invalid TypeNum requests with missing parameters to prevent unneeded logging, Render "X-Robots-Tag: noindex, nofollow" in all FE TypeNum answers. |

Documentation/Technical/Commands/Index.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Most of the Commands can be called via CLI or via Scheduler Backend Module (dire
1818
* Remove unknown visitors by age
1919
* Remove a defined visitor by uid
2020
* Remove visitors by a given property
21+
* Remove companies by a given property
2122
* Lead commands to get a summary mail for your sales team
2223
* Send an overall summary
2324
* Send a summary mail with known companies
@@ -142,6 +143,36 @@ Example usage:
142143
./vendor/bin/typo3 lux:cleanupVisitorsByProperty pagevisits.referrer "test.in2code.de" 0
143144
```
144145

146+
##### \In2code\Lux\Command\LuxCleanupCompaniesByPropertyCommand
147+
148+
Removes company records by a given property - e.g. all companies of a country that is not relevant for your sales.
149+
Remove means in this case not deleted=1 but really remove from database.
150+
151+
Relations to a removed company are always resolved, so no visitor points to a company that does not exist any more:
152+
153+
* **without** `--remove-visitors`: the related visitors are kept and only lose their relation to the company
154+
* **with** `--remove-visitors`: the related visitors and all rows from their related tables (pagevisits, downloads,
155+
log entries, fingerprints, attributes, ...) are removed as well
156+
157+
Use the property name of the company model - not the database field. So `countryCode` (and not `country_code`),
158+
`branchCode`, `revenueClass`, `sizeClass`, `title`, `domain`, `city`, `zip` or `region`.
159+
160+
Example usage:
161+
162+
```
163+
# Remove all companies of a country but keep their visitors (1 = exact match)
164+
./vendor/bin/typo3 lux:cleanupCompaniesByProperty countryCode cn 1
165+
166+
# Remove all companies of a country including all their visitors and the visitor related records
167+
./vendor/bin/typo3 lux:cleanupCompaniesByProperty countryCode cn 1 --remove-visitors
168+
169+
# Remove all companies with a title like "Testfirma" (0 = like)
170+
./vendor/bin/typo3 lux:cleanupCompaniesByProperty title Testfirma 0
171+
```
172+
173+
**Note:** Like `lux:cleanupVisitorsByProperty` this command handles up to 1000 records per run - simply call it
174+
again if there are more of them.
175+
145176

146177
#### Lead Commands
147178

ext_emconf.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
'description' => 'Living User Experience - LUX - the Marketing Automation tool for TYPO3.
55
Turn your visitors to leads. Identification and profiling of your visitors within your TYPO3 website.',
66
'category' => 'plugin',
7-
'version' => '44.2.0',
7+
'version' => '44.3.0',
88
'author' => 'Alex Kellner',
99
'author_email' => 'alexander.kellner@in2code.de',
1010
'author_company' => 'in2code.de',

0 commit comments

Comments
 (0)