<?php namespace OCA\Memento\Controller; require_once __DIR__ . '/datetimeConversion.php'; require_once __DIR__ . '/getUrlParameter.php'; use \Exception; use OCP\IRequest; use OCP\IServerContainer; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\DataDisplayResponse; class TimeGateController extends Controller { use MementoFinder; private $loggedInUserId; private $serverContainer; public function __construct( $AppName, IRequest $request, $UserId, IServerContainer $serverContainer ) { parent::__construct($AppName, $request); $this->loggedInUserId = $UserId; $this->serverContainer = $serverContainer; } /** * @NoAdminRequired * @NoCSRFRequired * @PublicPage */ public function singleUserTimeGate($userId, $url) { // XXX workaround, as nextcloud corrupts the $url parameter. $url = getUrlParameter("u/$userId/timegate"); $matchingMementos = $this->findSingleUserMementosForUrl($userId, $url); return $this->makeResponse($url, $matchingMementos); } /** * @NoAdminRequired * @NoCSRFRequired * @PublicPage */ public function allUsersTimeGate($url) { $url = getUrlParameter('timegate'); $matchingMementos = $this->findAllUsersMementosForUrl($url); return $this->makeResponse($url, $matchingMementos); } private function makeResponse($url, $matchingMementos) { // Choose one of the matched mementos, if any. if (count($matchingMementos) === 0) { // No matches. :( $message = "<h1>No snapshots found for requested URL. :(</h1>"; return new DataDisplayResponse($message, 404); } else if (count($matchingMementos) === 1) { // One match; no need to choose. $chosenMemento = $matchingMementos[0]; } else { // Multiple matches: choose based on requested date. $acceptDatetimeHeader = $this->request->getHeader('Accept-Datetime'); if ($acceptDatetimeHeader) { try { $requestedDatetime = datetimeStringToTimestamp($acceptDatetimeHeader); } catch (Exception $e) { return new DataDisplayResponse("Invalid Accept-Datetime header.", 400); } } else { // Not sending the header means requesting the most recent version. $requestedDatetime = time(); } // Pick the one closest to the requested date (either before or after it). $chosenMemento = minBy($matchingMementos, function ($matchingMemento) use ($requestedDatetime) { return abs($matchingMemento['datetime'] - $requestedDatetime); } ); } // Send a 302 Found redirect pointing to the chosen memento. $response = new RedirectResponse($chosenMemento['mementoUrl']); $response->setStatus(302); // Both the requested datetime and the authenticated user influence the response. $response->addHeader('Vary', 'accept-datetime, cookie'); // Add a link to the original(s) and to the timemap. $originalLinks = implode(", ", array_map( function ($originalUrl) { return "<$originalUrl>;rel=\"original\""; }, $chosenMemento['originalUrls'] )); // XXX hardcoding the route URL. $timeMapUrl = $this->serverContainer->getURLGenerator() ->getAbsoluteUrl("/apps/memento/timemap/$url"); $firstDatetime = datetimeTimestampToString($matchingMementos[0]['datetime']); $lastMemento = $matchingMementos[count($matchingMementos)-1]; $lastDatetime = datetimeTimestampToString($lastMemento['datetime']); $timeMapLink = "<$timeMapUrl>" . ";rel=\"timemap\"" . ";type=\"application/link-format\"" . ";from=\"$firstDatetime\";until=\"$lastDatetime\""; $response->addHeader('Link', "$originalLinks, $timeMapLink"); return $response; } } function minBy($array, $iteratee) { $values = array_map($iteratee, $array); $argmin = array_search(min($values), $values); return $array[$argmin]; }