nextcloud-memento/lib/Controller/ TimeGateController.php
121 lines
3.7 KiB

  1. <?php
  2. namespace OCA\Memento\Controller;
  3. require_once __DIR__ . '/findMementos.php';
  4. require_once __DIR__ . '/datetimeConversion.php';
  5. require_once __DIR__ . '/getUrlParameter.php';
  6. use \Exception;
  7. use OCP\IRequest;
  8. use OCP\IServerContainer;
  9. use OCP\AppFramework\Controller;
  10. use OCP\AppFramework\Http\RedirectResponse;
  11. use OCP\AppFramework\Http\DataDisplayResponse;
  12. class TimeGateController extends Controller {
  13. use MementoFinder;
  14. private $loggedInUserId;
  15. private $serverContainer;
  16. public function __construct(
  17. $AppName,
  18. IRequest $request,
  19. $UserId,
  20. IServerContainer $serverContainer
  21. ) {
  22. parent::__construct($AppName, $request);
  23. $this->loggedInUserId = $UserId;
  24. $this->serverContainer = $serverContainer;
  25. }
  26. /**
  27. * @NoAdminRequired
  28. * @NoCSRFRequired
  29. * @PublicPage
  30. */
  31. public function singleUserTimeGate($userId, $url) {
  32. // XXX workaround, as nextcloud corrupts the $url parameter.
  33. $url = getUrlParameter("u/$userId/timegate");
  34. $matchingMementos = $this->findSingleUserMementosForUrl($userId, $url);
  35. return $this->makeResponse($url, $matchingMementos);
  36. }
  37. /**
  38. * @NoAdminRequired
  39. * @NoCSRFRequired
  40. * @PublicPage
  41. */
  42. public function allUsersTimeGate($url) {
  43. $url = getUrlParameter('timegate');
  44. $matchingMementos = $this->findAllUsersMementosForUrl($url);
  45. return $this->makeResponse($url, $matchingMementos);
  46. }
  47. private function makeResponse($url, $matchingMementos) {
  48. // Choose one of the matched mementos, if any.
  49. if (count($matchingMementos) === 0) {
  50. // No matches. :(
  51. $message = "<h1>No snapshots found for requested URL. :(</h1>";
  52. return new DataDisplayResponse($message, 404);
  53. } else if (count($matchingMementos) === 1) {
  54. // One match; no need to choose.
  55. $chosenMemento = $matchingMementos[0];
  56. } else {
  57. // Multiple matches: choose based on requested date.
  58. $acceptDatetimeHeader = $this->request->getHeader('Accept-Datetime');
  59. if ($acceptDatetimeHeader) {
  60. try {
  61. $requestedDatetime = datetimeStringToTimestamp($acceptDatetimeHeader);
  62. } catch (Exception $e) {
  63. return new DataDisplayResponse("Invalid Accept-Datetime header.", 400);
  64. }
  65. } else {
  66. // Not sending the header means requesting the most recent version.
  67. $requestedDatetime = time();
  68. }
  69. // Pick the one closest to the requested date (either before or after it).
  70. $chosenMemento = minBy($matchingMementos,
  71. function ($matchingMemento) use ($requestedDatetime) {
  72. return abs($matchingMemento['datetime'] - $requestedDatetime);
  73. }
  74. );
  75. }
  76. // Send a 302 Found redirect pointing to the chosen memento.
  77. $response = new RedirectResponse($chosenMemento['mementoUrl']);
  78. $response->setStatus(302);
  79. // Both the requested datetime and the authenticated user influence the response.
  80. $response->addHeader('Vary', 'accept-datetime, cookie');
  81. // Add a link to the original(s) and to the timemap.
  82. $originalLinks = implode(", ", array_map(
  83. function ($originalUrl) { return "<$originalUrl>;rel=\"original\""; },
  84. $chosenMemento['originalUrls']
  85. ));
  86. // XXX hardcoding the route URL.
  87. $timeMapUrl = $this->serverContainer->getURLGenerator()
  88. ->getAbsoluteUrl("/apps/memento/timemap/$url");
  89. $firstDatetime = datetimeTimestampToString($matchingMementos[0]['datetime']);
  90. $lastMemento = $matchingMementos[count($matchingMementos)-1];
  91. $lastDatetime = datetimeTimestampToString($lastMemento['datetime']);
  92. $timeMapLink = "<$timeMapUrl>"
  93. . ";rel=\"timemap\""
  94. . ";type=\"application/link-format\""
  95. . ";from=\"$firstDatetime\";until=\"$lastDatetime\"";
  96. $response->addHeader('Link', "$originalLinks, $timeMapLink");
  97. return $response;
  98. }
  99. }
  100. function minBy($array, $iteratee) {
  101. $values = array_map($iteratee, $array);
  102. $argmin = array_search(min($values), $values);
  103. return $array[$argmin];
  104. }