hits() as $hit) { // Normalised exactly as a redirect is, because that is what these // are eventually compared against: "/x/" and "/x?ref=1" are the // same broken address and must not become two rows. $path = Redirect::normalise((string) $hit['path']); if ($this->isNoise($path)) { continue; } MissingUrl::report( $path, // The frontend usually knows which language it was rendering. // When it does not, the negotiated locale is the same answer: // it comes from the Accept-Language the frontend forwarded. filled($hit['locale'] ?? null) ? (string) $hit['locale'] : app()->getLocale(), filled($hit['referrer'] ?? null) ? (string) $hit['referrer'] : null, ); $recorded++; } return response()->json([ 'data' => [ 'received' => count($request->hits()), 'recorded' => $recorded, ], ], Response::HTTP_ACCEPTED) ->header('Cache-Control', 'no-store, private'); } /** * POST /api/public/v1/telemetry/views * * What was read. The panel has always known what exists and what is * published, and never whether any of it gets opened — which is the fact * behind most editorial decisions: what to translate next, whether the * featured project is one anybody looks at, whether a page earns the work * of keeping it accurate. * * There is no visitor in any of this. A report says a record was rendered * in a language; what is stored is a daily total per record. No address, * no session, no user agent, no time of day — nothing that could be * assembled back into a person. */ public function views(ReportViewsRequest $request): JsonResponse { $recorded = 0; foreach ($request->views() as $view) { $locale = filled($view['locale'] ?? null) ? (string) $view['locale'] : app()->getLocale(); $record = ContentTypes::resolve((string) $view['type'], (string) $view['slug'], $locale); // A slug that no longer resolves is a 404, and the endpoint next // door exists to hear about those. Counting it here would attach // somebody's reading to nothing. if ($record === null) { continue; } ContentView::record($record, $locale, (int) ($view['count'] ?? 1)); $recorded++; } return response()->json([ 'data' => [ 'received' => count($request->views()), 'recorded' => $recorded, ], ], Response::HTTP_ACCEPTED) ->header('Cache-Control', 'no-store, private'); } /** * A scan for something that was never here. * * Dropped rather than stored, and dropped on this side rather than left to * the frontend: the list is only useful while a person is willing to read * it, and one afternoon of a bot looking for WordPress would end that. */ protected function isNoise(string $path): bool { foreach ((array) config('site.telemetry.ignore', []) as $pattern) { if (fnmatch((string) $pattern, $path, FNM_CASEFOLD)) { return true; } } return false; } }