Completed
Push — master ( 19732d...076741 )
by Paweł
11:47
created

AnalyticsEventConsumer::getPathFromUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Core Bundle.
7
 *
8
 * Copyright 2017 Sourcefabric z.ú. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2017 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\CoreBundle\Consumer;
18
19
use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface;
20
use PhpAmqpLib\Message\AMQPMessage;
21
use SWP\Bundle\AnalyticsBundle\Model\ArticleEventInterface;
22
use SWP\Bundle\AnalyticsBundle\Services\ArticleStatisticsServiceInterface;
23
use SWP\Bundle\ContentBundle\Model\RouteInterface;
24
use SWP\Bundle\CoreBundle\Model\ArticleInterface;
25
use SWP\Component\MultiTenancy\Context\TenantContextInterface;
26
use SWP\Component\MultiTenancy\Resolver\TenantResolver;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
29
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
30
31
/**
32
 * Class AnalyticsEventConsumer.
33
 */
34
final class AnalyticsEventConsumer implements ConsumerInterface
35
{
36
    /**
37
     * @var ArticleStatisticsServiceInterface
38
     */
39
    private $articleStatisticsService;
40
41
    /**
42
     * @var TenantResolver
43
     */
44
    private $tenantResolver;
45
46
    /**
47
     * @var TenantContextInterface
48
     */
49
    private $tenantContext;
50
51
    /**
52
     * @var UrlMatcherInterface
53
     */
54
    private $matcher;
55
56
    /**
57
     * AnalyticsEventConsumer constructor.
58
     *
59
     * @param ArticleStatisticsServiceInterface $articleStatisticsService
60
     * @param TenantResolver                    $tenantResolver
61
     * @param TenantContextInterface            $tenantContext
62
     */
63
    public function __construct(
64
        ArticleStatisticsServiceInterface $articleStatisticsService,
65
        TenantResolver $tenantResolver,
66
        TenantContextInterface $tenantContext,
67
        UrlMatcherInterface $matcher
68
    ) {
69
        $this->articleStatisticsService = $articleStatisticsService;
70
        $this->tenantResolver = $tenantResolver;
71
        $this->tenantContext = $tenantContext;
72
        $this->matcher = $matcher;
73
    }
74
75
    /**
76
     * @param AMQPMessage $message
77
     *
78
     * @return bool|mixed
79
     */
80
    public function execute(AMQPMessage $message)
81
    {
82
        /** @var Request $request */
83
        $request = unserialize($message->getBody());
84
        if (!$request instanceof Request) {
85
            return ConsumerInterface::MSG_REJECT;
86
        }
87
88
        $this->setTenant($request);
89
90
        if ($request->query->has('articleId')) {
91
            $this->handleArticlePageViews($request);
92
93
            echo 'Pageview for article '.$request->query->get('articleId')." was processed \n";
94
        }
95
96
        if ($request->attributes->has('data') && ArticleEventInterface::ACTION_IMPRESSION === $request->query->get('type')) {
97
            $this->handleArticleImpressions($request);
98
99
            echo "Article impressions were processed \n";
100
        }
101
102
        return ConsumerInterface::MSG_ACK;
103
    }
104
105
    private function handleArticleImpressions(Request $request): void
106
    {
107
        $articles = [];
108
        if (!\is_array($request->attributes->get('data'))) {
109
            return;
110
        }
111
112
        foreach ($request->attributes->get('data') as $url) {
113
            try {
114
                $route = $this->matcher->match($this->getFragmentFromUrl($url, 'path'));
115
                if (isset($route['_article_meta']) && $route['_article_meta']->getValues() instanceof ArticleInterface) {
116
                    $articleId = $route['_article_meta']->getValues()->getId();
117
                    if (!\array_key_exists($articleId, $articles)) {
118
                        $articles[$articleId] = $route['_article_meta']->getValues();
119
                    }
120
                }
121
            } catch (ResourceNotFoundException $e) {
122
                //ignore
123
            }
124
        }
125
126
        foreach ($articles as $article) {
127
            $this->articleStatisticsService->addArticleEvent(
128
                (int) $article->getId(),
129
                ArticleEventInterface::ACTION_IMPRESSION,
130
                $this->getImpressionSource($request)
131
            );
132
        }
133
    }
134
135
    private function handleArticlePageViews(Request $request): void
136
    {
137
        $articleId = $request->query->get('articleId', null);
138
        if (null !== $articleId) {
139
            $this->articleStatisticsService->addArticleEvent((int) $articleId, ArticleEventInterface::ACTION_PAGEVIEW, [
140
                'pageViewSource' => $this->getPageViewSource($request),
141
            ]);
142
        }
143
    }
144
145
    private function getImpressionSource(Request $request): array
146
    {
147
        $source = [];
148
        $referrer = $request->server->get('HTTP_REFERER');
149
        if (null === $referrer) {
150
            return $source;
151
        }
152
153
        $route = $this->matcher->match($this->getFragmentFromUrl($referrer, 'path'));
154
        if (isset($route['_article_meta']) && $route['_article_meta']->getValues() instanceof ArticleInterface) {
155
            $source[ArticleStatisticsServiceInterface::KEY_IMPRESSION_TYPE] = 'article';
156
            $source[ArticleStatisticsServiceInterface::KEY_IMPRESSION_SOURCE_ARTICLE] = $route['_article_meta']->getValues();
157
        } elseif (isset($route['_route_meta']) && $route['_route_meta']->getValues() instanceof RouteInterface) {
158
            $source[ArticleStatisticsServiceInterface::KEY_IMPRESSION_TYPE] = 'route';
159
            $source[ArticleStatisticsServiceInterface::KEY_IMPRESSION_SOURCE_ROUTE] = $route['_route_meta']->getValues();
160
        } elseif (isset($route['_route']) && 'homepage' === $route['_route']) {
161
            $source[ArticleStatisticsServiceInterface::KEY_IMPRESSION_TYPE] = 'homepage';
162
        }
163
164
        return $source;
165
    }
166
167
    private function getPageViewSource(Request $request): string
168
    {
169
        $pageViewReferer = $request->query->get('ref', null);
170
        if (null !== $pageViewReferer) {
171
            $refererHost = $this->getFragmentFromUrl($pageViewReferer, 'host');
172
            if ($refererHost && $this->isHostMatchingTenant($refererHost)) {
173
                return ArticleEventInterface::PAGEVIEW_SOURCE_INTERNAL;
174
            }
175
        }
176
177
        return ArticleEventInterface::PAGEVIEW_SOURCE_EXTERNAL;
178
    }
179
180
    private function getFragmentFromUrl(string $url, string $fragment): ?string
181
    {
182
        $fragments = \parse_url($url);
183
        if (!\array_key_exists($fragment, $fragments)) {
184
            return null;
185
        }
186
187
        return str_replace('/app_dev.php', '', $fragments[$fragment]);
188
    }
189
190
    private function isHostMatchingTenant(string $host): bool
191
    {
192
        $tenant = $this->tenantContext->getTenant();
193
        $tenantHost = $tenant->getDomainName();
194
        if (null !== ($subdomain = $tenant->getSubdomain())) {
195
            $tenantHost = $subdomain.'.'.$tenantHost;
196
        }
197
198
        return $host === $tenantHost;
199
    }
200
201
    /**
202
     * @param Request $request
203
     */
204
    private function setTenant(Request $request): void
205
    {
206
        $this->tenantContext->setTenant($this->tenantResolver->resolve($request->getHost()));
207
    }
208
}
209