Skip to content

Commit 5954ccd

Browse files
committed
[FEATURE] Add source view in backend
1 parent 6261e76 commit 5954ccd

File tree

22 files changed

+1007
-27
lines changed

22 files changed

+1007
-27
lines changed

Classes/Controller/AnalysisController.php

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use In2code\Lux\Domain\DataProvider\NewsvisistsDataProvider;
1616
use In2code\Lux\Domain\DataProvider\PagevisistsDataProvider;
1717
use In2code\Lux\Domain\DataProvider\ReferrerAmountDataProvider;
18+
use In2code\Lux\Domain\DataProvider\ReferrerCategoryDataProvider;
1819
use In2code\Lux\Domain\DataProvider\SearchDataProvider;
1920
use In2code\Lux\Domain\DataProvider\SocialMediaDataProvider;
2021
use In2code\Lux\Domain\DataProvider\UtmCampaignDataProvider;
@@ -25,6 +26,7 @@
2526
use In2code\Lux\Domain\Model\News;
2627
use In2code\Lux\Domain\Model\Page;
2728
use In2code\Lux\Domain\Model\Transfer\FilterDto;
29+
use In2code\Lux\Domain\Service\Referrer\Readable;
2830
use In2code\Lux\Exception\ArgumentsException;
2931
use In2code\Lux\Exception\AuthenticationException;
3032
use In2code\Lux\Utility\BackendUtility;
@@ -126,6 +128,54 @@ public function contentCsvAction(FilterDto $filter): ResponseInterface
126128
return $this->csvResponse();
127129
}
128130

131+
/**
132+
* @return void
133+
* @throws NoSuchArgumentException
134+
*/
135+
public function initializeSourcesAction(): void
136+
{
137+
$this->setFilter();
138+
}
139+
140+
/**
141+
* Sources with referrers
142+
*
143+
* @param FilterDto $filter
144+
* @param string $export
145+
* @return ResponseInterface
146+
* @throws ExceptionDbal
147+
*/
148+
public function sourcesAction(FilterDto $filter, string $export = ''): ResponseInterface
149+
{
150+
if ($export === 'csv') {
151+
return (new ForwardResponse('sourcesCsv'))->withArguments(['filter' => $filter]);
152+
}
153+
154+
$values = [
155+
'filter' => $filter,
156+
'referrers' => $this->pagevisitsRepository->getReferrers($filter),
157+
'sourceCategories' => GeneralUtility::makeInstance(Readable::class)->getAllKeys(),
158+
'categoryData' => GeneralUtility::makeInstance(ReferrerCategoryDataProvider::class, $filter),
159+
];
160+
$this->moduleTemplate->assignMultiple($values);
161+
162+
$this->addDocumentHeaderForCurrentController();
163+
return $this->defaultRendering();
164+
}
165+
166+
/**
167+
* @param FilterDto $filter
168+
* @return ResponseInterface
169+
* @throws ExceptionDbal
170+
*/
171+
public function sourcesCsvAction(FilterDto $filter): ResponseInterface
172+
{
173+
$this->view->assignMultiple([
174+
'referrers' => $this->pagevisitsRepository->getReferrers($filter),
175+
]);
176+
return $this->csvResponse();
177+
}
178+
129179
/**
130180
* @return void
131181
* @throws NoSuchArgumentException
@@ -475,6 +525,38 @@ public function detailAjaxPage(ServerRequestInterface $request): ResponseInterfa
475525
return $response;
476526
}
477527

528+
/**
529+
* AJAX action to show a detail view coming from sourceAction
530+
*
531+
* @param ServerRequestInterface $request
532+
* @return ResponseInterface
533+
* @noinspection PhpUnused
534+
* @throws ExceptionDbal
535+
* @throws ArgumentsException
536+
*/
537+
public function detailAjaxSource(ServerRequestInterface $request): ResponseInterface
538+
{
539+
$filter = BackendUtility::getFilterFromSession(
540+
'sources',
541+
'Analysis',
542+
['searchterm' => (string)$request->getQueryParams()['referrerDomain'], 'limit' => 10]
543+
);
544+
$standaloneView = ObjectUtility::getStandaloneView();
545+
$standaloneView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
546+
'EXT:lux/Resources/Private/Templates/Analysis/SourcesDetailAjax.html'
547+
));
548+
$standaloneView->setPartialRootPaths(['EXT:lux/Resources/Private/Partials/']);
549+
$standaloneView->assignMultiple([
550+
'pagevisits' => $this->pagevisitsRepository->findByReferrerDomain($filter),
551+
// 'numberOfVisitorsData' => GeneralUtility::makeInstance(PagevisistsDataProvider::class, $filter),
552+
]);
553+
$response = GeneralUtility::makeInstance(JsonResponse::class);
554+
/** @var StreamInterface $stream */
555+
$stream = $response->getBody();
556+
$stream->write(json_encode(['html' => $standaloneView->render()]));
557+
return $response;
558+
}
559+
478560
/**
479561
* AJAX action to show a detail view for news
480562
*
@@ -620,7 +702,7 @@ public function detailAjaxLinklistener(ServerRequestInterface $request): Respons
620702
*/
621703
protected function addDocumentHeaderForCurrentController(): void
622704
{
623-
$actions = ['dashboard', 'content'];
705+
$actions = ['dashboard', 'content', 'sources'];
624706
if ($this->newsvisitRepository->isTableFilled()) {
625707
$actions[] = 'news';
626708
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace In2code\Lux\Domain\DataProvider;
5+
6+
use Doctrine\DBAL\Driver\Exception as ExceptionDbalDriver;
7+
use Doctrine\DBAL\Exception as ExceptionDbal;
8+
use In2code\Lux\Domain\Repository\PagevisitRepository;
9+
use In2code\Lux\Utility\LocalizationUtility;
10+
use TYPO3\CMS\Core\Utility\GeneralUtility;
11+
12+
class ReferrerCategoryDataProvider extends AbstractDataProvider
13+
{
14+
/**
15+
* Set values like:
16+
* [
17+
* 'amounts' => [
18+
* 120,
19+
* 88
20+
* ],
21+
* 'titles' => [
22+
* 'socialMedia',
23+
* 'aiChats',
24+
* ]
25+
* ]
26+
*
27+
* @return void
28+
*/
29+
public function prepareData(): void
30+
{
31+
/** @var PagevisitRepository $pagevisitRepository */
32+
$pagevisitRepository = GeneralUtility::makeInstance(PagevisitRepository::class);
33+
$titles = $amounts = [];
34+
$counter = 0;
35+
foreach ($pagevisitRepository->getReferrerCategoryAmounts($this->filter) as $sourceKey => $amount) {
36+
$titles[] = LocalizationUtility::translateByKey('readablereferrer.' . $sourceKey);
37+
$amounts[] = $amount;
38+
if ($counter >= 5) {
39+
break;
40+
}
41+
$counter++;
42+
}
43+
$this->data = ['amounts' => $amounts, 'titles' => $titles];
44+
}
45+
}

Classes/Domain/Repository/PagevisitRepository.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ public function findLatestPagevisitsWithCompanies(FilterDto $filter): QueryResul
130130
return $query->execute();
131131
}
132132

133+
public function findByReferrerDomain(FilterDto $filter): array
134+
{
135+
if (MathUtility::canBeInterpretedAsInteger($filter->isSearchtermSet()) === false) {
136+
throw new ArgumentsException('Filter searchterm must be filled', 1752775565);
137+
}
138+
139+
$sql = 'select pv.uid from ' . Pagevisit::TABLE_NAME . ' pv'
140+
. ' where pv.referrer like "https://' . $filter->getSearchterm() . '%" '
141+
. $this->extendWhereClauseWithFilterTime($filter, true, 'pv')
142+
. $this->extendWhereClauseWithFilterSite($filter, 'pv')
143+
. ' order by pv.crdate desc'
144+
. ' limit ' . ($filter->isLimitSet() ? $filter->getLimit() : 750);
145+
$connection = DatabaseUtility::getConnectionForTable(Pagevisit::TABLE_NAME);
146+
$pagevisitIdentifiers = $connection->executeQuery($sql)->fetchFirstColumn();
147+
return $this->convertIdentifiersToObjects($pagevisitIdentifiers, Pagevisit::TABLE_NAME);
148+
}
149+
133150
/**
134151
* @param DateTime $start
135152
* @param DateTime $end
@@ -363,6 +380,101 @@ public function getAmountOfSocialMediaReferrers(FilterDto $filter): array
363380
return $result;
364381
}
365382

383+
/**
384+
* [
385+
* [
386+
* 'referrer_domain' => 'x.com',
387+
* 'count' => 123,
388+
* 'identified_count' => 34,
389+
* 'children' => [QueryResult],
390+
* ],
391+
* [
392+
* 'referrer_domain' => 'openai.com',
393+
* 'count' => 25,
394+
* 'identified_count' => 12,
395+
* 'children' => [QueryResult],
396+
* ],
397+
* ]
398+
*
399+
* @param FilterDto $filter
400+
* @return array
401+
* @throws ExceptionDbal
402+
*/
403+
public function getReferrers(FilterDto $filter): array
404+
{
405+
$readable = GeneralUtility::makeInstance(Readable::class);
406+
$grouped = [];
407+
foreach ($this->getAmountOfReferrerDomains($filter) as $row) {
408+
$row['identified_count'] = $this->extendRowIdentified($row, $filter);
409+
$row = $this->extendRowWithChildren($row, $filter);
410+
$key = $readable->getKeyFromHost($row['referrer_domain']) ?: 'other';
411+
$grouped[$key][] = $row;
412+
}
413+
return $grouped;
414+
}
415+
416+
/**
417+
* [
418+
* [
419+
* 'referrer_domain' => 'x.com',
420+
* 'count' => 123,
421+
* ],
422+
* [
423+
* 'referrer_domain' => 'openai.com',
424+
* 'count' => 25,
425+
* ],
426+
* ]
427+
*
428+
* @param FilterDto $filter
429+
* @return array
430+
* @throws ExceptionDbal
431+
*/
432+
protected function getAmountOfReferrerDomains(FilterDto $filter): array
433+
{
434+
$connection = DatabaseUtility::getConnectionForTable(Pagevisit::TABLE_NAME);
435+
$sql = 'select substring_index(substring_index(referrer, \'://\', -1), \'/\', 1) referrer_domain, count(*) count';
436+
$sql .= ' from ' . Pagevisit::TABLE_NAME . ' pv';
437+
$sql .= ' where pv.deleted = 0 and pv.hidden = 0 and pv.referrer != \'\'';
438+
$sql .= $this->extendWhereClauseWithFilterSearchterms($filter, 'pv', 'referrer');
439+
$sql .= $this->extendWhereClauseWithFilterTime($filter);
440+
$sql .= $this->extendWhereClauseWithFilterSite($filter);
441+
$sql .= $this->extendWhereClauseWithFilterDomain($filter);
442+
$sql .= ' group by referrer_domain order by count desc;';
443+
$rows = $connection->executeQuery($sql)->fetchAllAssociative();
444+
return $rows;
445+
}
446+
447+
protected function extendRowIdentified(array $row, FilterDto $filter): int
448+
{
449+
$connection = DatabaseUtility::getConnectionForTable(Pagevisit::TABLE_NAME);
450+
$sql = 'select count(*) identified_count';
451+
$sql .= ' from tx_lux_domain_model_visitor v';
452+
$sql .= ' where v.identified = 1';
453+
$sql .= ' and exists (';
454+
$sql .= 'select 1';
455+
$sql .= ' from tx_lux_domain_model_pagevisit pv';
456+
$sql .= ' where pv.visitor = v.uid and pv.referrer like "https://' . $row['referrer_domain'] . '%"';
457+
$sql .= $this->extendWhereClauseWithFilterTime($filter, true, 'pv');
458+
$sql .= $this->extendWhereClauseWithFilterSite($filter, 'pv');
459+
$sql .= ')';
460+
return (int)$connection->executeQuery($sql)->fetchOne();
461+
}
462+
463+
protected function extendRowWithChildren(array $row, FilterDto $filter): array
464+
{
465+
$query = $this->createQuery();
466+
$logicalAnd = [
467+
$query->like('referrer', 'https://' . $row['referrer_domain'] . '%'),
468+
];
469+
$logicalAnd = $this->extendLogicalAndWithFilterConstraintsForCrdate($filter, $query, $logicalAnd);
470+
$logicalAnd = $this->extendLogicalAndWithFilterConstraintsForSite($filter, $query, $logicalAnd);
471+
$query->matching($query->logicalAnd(...$logicalAnd));
472+
$query->setOrderings(['crdate' => QueryInterface::ORDER_DESCENDING]);
473+
$query->setLimit($row['count']);
474+
$row['children'] = $query->execute();
475+
return $row;
476+
}
477+
366478
/**
367479
* Get social media amount of referrers from link shortener (part of luxenterprise)
368480
*
@@ -380,6 +492,25 @@ protected function getAmountOfSocialMediaReferrersFromShorteners(array $result,
380492
return $result;
381493
}
382494

495+
public function getReferrerCategoryAmounts(FilterDto $filter): array
496+
{
497+
$readable = GeneralUtility::makeInstance(Readable::class);
498+
$amounts = [];
499+
foreach ($readable->getAllKeysWithDomainsForQuery() as $key => $valueRegEx) {
500+
$connection = DatabaseUtility::getConnectionForTable(Pagevisit::TABLE_NAME);
501+
$sql = 'SELECT count(*) as count';
502+
$sql .= ' from ' . Pagevisit::TABLE_NAME . ' pv';
503+
$sql .= ' where referrer RLIKE \'^https://(' . $valueRegEx . ')/\'';
504+
$sql .= $this->extendWhereClauseWithFilterSearchterms($filter, 'pv', 'referrer');
505+
$sql .= $this->extendWhereClauseWithFilterTime($filter);
506+
$sql .= $this->extendWhereClauseWithFilterSite($filter);
507+
$sql .= $this->extendWhereClauseWithFilterDomain($filter);
508+
$amounts[$key] = $connection->executeQuery($sql)->fetchOne();
509+
}
510+
arsort($amounts);
511+
return $amounts;
512+
}
513+
383514
/**
384515
* @param FilterDto $filter
385516
* @return array
@@ -509,4 +640,33 @@ public function compareAmountPerPage(int $pageIdentifier, FilterDto $filter1, Fi
509640
$amount2 = $this->findAmountPerPage($pageIdentifier, $filter2);
510641
return $amount1 - $amount2;
511642
}
643+
644+
/**
645+
* Domain is misleading here - this function is reused to search for given domains by a category
646+
*
647+
* @param FilterDto $filter
648+
* @param string $table
649+
* @return string
650+
*/
651+
protected function extendWhereClauseWithFilterDomain(FilterDto $filter, string $table = ''): string
652+
{
653+
$sql = '';
654+
if ($filter->isDomainSet()) {
655+
$field = 'referrer';
656+
if ($table !== '') {
657+
$field = $table . '.' . $field;
658+
}
659+
$readable = GeneralUtility::makeInstance(Readable::class);
660+
$domains = $readable->getDomainsFromCategory($filter->getDomain());
661+
662+
if ($domains !== []) {
663+
$conditions = [];
664+
foreach ($domains as $domain) {
665+
$conditions[] = $field . ' LIKE "https://' . $domain . '%"';
666+
}
667+
$sql .= ' and (' . implode(' OR ', $conditions) . ')';
668+
}
669+
}
670+
return $sql;
671+
}
512672
}

0 commit comments

Comments
 (0)