TimeGateController.php 3.7 KiB

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