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