PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Http/Endpoints/DistributionEndpoint.php +144 -0 2.0.0 → 2.1.1 View file →
@@ -1,0 +1,144 @@
1 +<?php
2 +
3 +declare(strict_types=1);
4 +
5 +namespace Metricool\Http\Endpoints;
6 +
7 +use Metricool\Http\Endpoints\Responses\DistributionResponse;
8 +use Metricool\Http\Endpoints\Responses\Statistics\CountriesResponse;
9 +use Metricool\Http\Endpoints\Responses\Statistics\RefererResponse;
10 +use Metricool\Http\Metricool\DTOs\DistributionDTO;
11 +use Metricool\Http\Metricool\MetricoolApi;
12 +use Metricool\Interfaces\SingleEndpointInterface;
13 +use Metricool\Services\DashboardService;
14 +use Metricool\Support\Helpers\Collection;
15 +use Metricool\Support\Validation\Validator;
16 +use Metricool\Traits\HasAllowlistControl;
17 +use Metricool\Traits\HasRestAccess;
18 +
19 +class DistributionEndpoint implements SingleEndpointInterface
20 +{
21 + use HasRestAccess;
22 + use HasAllowlistControl;
23 +
24 + public const ROUTE = 'distribution';
25 +
26 + private const METRICS_RESPONSE_MAPPER = [
27 + 'countries' => CountriesResponse::class,
28 + 'referers' => RefererResponse::class,
29 + ];
30 +
31 + public MetricoolApi $metricoolApi;
32 + public DashboardService $dashboard;
33 +
34 + public function __construct(MetricoolApi $metricoolApi, DashboardService $dashboard)
35 + {
36 + $this->metricoolApi = $metricoolApi;
37 + $this->dashboard = $dashboard;
38 + }
39 +
40 + /**
41 + * @inheritDoc
42 + */
43 + public function registerRoute(): string
44 + {
45 + return self::ROUTE . '/(?P<metric>[^/]+)';
46 + }
47 +
48 + /**
49 + * Only enable this endpoint when onboarding is completed
50 + */
51 + public function enabled(): bool
52 + {
53 + return $this->dashboard->isOnboardingCompleted();
54 + }
55 +
56 + /**
57 + * @inheritDoc
58 + */
59 + public function registerArguments(): array
60 + {
61 + return [
62 + 'methods' => \WP_REST_Server::READABLE,
63 + 'callback' => [$this, 'callback'],
64 + 'middleware' => ['metricool:auth', 'metricool:blog_id'],
65 + ];
66 + }
67 +
68 + /**
69 + * Method will dynamically request the requested statistic. If the metric
70 + * is filterable and filters are provided, it will apply them before
71 + * retrieving the data.
72 + *
73 + * GET /wp-json/metricool/v1/distribution/countries?filters[start]=20250618&filters[end]=20250718&filters[country]=nl
74 + */
75 + public function callback(\WP_REST_Request $request): \WP_REST_Response
76 + {
77 + $validated = Validator::validate($request->get_params(), [
78 + 'metric' => 'required|string|in:countries,referers',
79 + 'filters' => 'array',
80 + ]);
81 +
82 + try {
83 + $response = $this->buildResponse($validated);
84 + } catch (\Exception $e) {
85 + return $this->sendHttpErrorResponse(__('Failed to load Analytics data', 'metricool'), $e->getMessage(), $e->getCode());
86 + }
87 +
88 + return $this->sendHttpResponse($response);
89 + }
90 +
91 + /**
92 + * Build the specific Analytics response for the endpoint. This is mainly
93 + * used in the plugin Dashboard to reflect non-realtime statistics.
94 + * Building it server side prevents client-side complexity.
95 + *
96 + * @throws \Exception
97 + */
98 + private function buildResponse(array $validated): array
99 + {
100 + $metric = $validated['metric'];
101 + $requestFilters = $validated['filters'] ?? [];
102 +
103 + // Load the statistics
104 + $statistics = $this->getStatisticsForMetric($metric, $requestFilters);
105 +
106 + // Find the associated response object for the metric
107 + $response = $this->createResponseObjectFromMetric($metric, $statistics);
108 +
109 + return $response->body();
110 + }
111 +
112 + /**
113 + * Loads the results from the Metricool API
114 + * @return Collection|DistributionDTO[]
115 + */
116 + protected function getStatisticsForMetric(string $metric, array $filters): Collection
117 + {
118 + $statisticsModule = $this->metricoolApi->statistics();
119 +
120 + // Load the results
121 + $metricModule = $statisticsModule->$metric();
122 + if (!empty($filters)) {
123 + $metricModule->filter($filters);
124 + }
125 +
126 + return $metricModule->get();
127 + }
128 +
129 + /**
130 + * Find the response that matches the requested metric or throw an exception.
131 + * Each metric has its own specific serialisation of the results and chartData
132 + * @param Collection|DistributionDTO[] $statistics
133 + */
134 + protected function createResponseObjectFromMetric(string $metric, Collection $statistics): DistributionResponse
135 + {
136 + if (!array_key_exists($metric, self::METRICS_RESPONSE_MAPPER)) {
137 + throw new \InvalidArgumentException(esc_html("Metric $metric is not accepted by this endpoint"));
138 + }
139 +
140 + $response = self::METRICS_RESPONSE_MAPPER[$metric];
141 +
142 + return new $response($statistics);
143 + }
144 +}