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