nextcloud-memento/lib/Controller/ TimeGateController.php
94 lines
3.0 KiB

  1. <?php
  2. namespace OCA\Memento\Controller;
  3. require_once __DIR__ . '/findMementos.php';
  4. require_once __DIR__ . '/datetimeConversion.php';
  5. use OCP\IRequest;
  6. use OCP\IURLGenerator;
  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. private $userFolder;
  13. private $URLGenerator;
  14. public function __construct(
  15. $AppName,
  16. IRequest $request,
  17. $UserId,
  18. IServerContainer $serverContainer,
  19. IURLGenerator $URLGenerator
  20. ) {
  21. parent::__construct($AppName, $request);
  22. $this->userFolder = $serverContainer->getUserFolder($UserId);
  23. $this->URLGenerator = $URLGenerator;
  24. }
  25. /**
  26. * @NoAdminRequired
  27. * @NoCSRFRequired
  28. */
  29. public function timeGate($url) {
  30. $matchingMementos = findMementos($this->userFolder, $url);
  31. // Choose one of the matched mementos, if any.
  32. if (count($matchingMementos) === 0) {
  33. // No matches. :(
  34. $message = "<h1>No snapshots found for requested URL. :(</h1>";
  35. return new DataDisplayResponse($message, 404);
  36. } else if (count($matchingMementos) === 1) {
  37. // One match; no need to choose.
  38. $chosenMemento = $matchingMementos[0];
  39. } else {
  40. // Multiple matches: choose based on requested date.
  41. $acceptDatetimeHeader = $this->request->getHeader('Accept-Datetime');
  42. if ($acceptDatetimeHeader) {
  43. try {
  44. $requestedDatetime = datetimeStringToTimestamp($acceptDatetimeHeader);
  45. } catch (Exception $e) {
  46. return new DataDisplayResponse("Invalid Accept-Datetime header.", 400);
  47. }
  48. } else {
  49. // Not sending the header means requesting the most recent version.
  50. $requestedDatetime = time();
  51. }
  52. // Pick the one closest to the requested date (either before or after it).
  53. $chosenMemento = minBy($matchingMementos,
  54. function ($matchingMemento) use ($requestedDatetime) {
  55. return abs($matchingMemento['datetime'] - $requestedDatetime);
  56. }
  57. );
  58. }
  59. // Send a 302 Found redirect pointing to the chosen memento.
  60. $response = new RedirectResponse($chosenMemento['mementoUrl']);
  61. $response->setStatus(302);
  62. $response->addHeader('Vary', 'accept-datetime');
  63. // Add a link to the original and to the timemap.
  64. $originalLink = "<{$chosenMemento['originalUrl']}>;rel=\"original\"";
  65. // XXX hardcoding the route URL.
  66. $timeMapUrl = $this->URLGenerator->getAbsoluteUrl("/apps/memento/timemap/$url");
  67. $firstDatetime = datetimeTimestampToString($matchingMementos[0]['datetime']);
  68. $lastMemento = $matchingMementos[count($matchingMementos)-1];
  69. $lastDatetime = datetimeTimestampToString($lastMemento['datetime']);
  70. $timeMapLink = "<$timeMapUrl>"
  71. . ";rel=\"timemap\""
  72. . ";type=\"application/link-format\""
  73. . ";from=\"$firstDatetime\";until=\"$lastDatetime\"";
  74. $response->addHeader('Link', "$originalLink, $timeMapLink");
  75. return $response;
  76. }
  77. }
  78. function minBy($array, $iteratee) {
  79. // is there any simpler way for this in php?
  80. $values = array_map($iteratee, $array);
  81. $argmin = array_search(min($values), $values);
  82. return $array[$argmin];
  83. }