nextcloud-memento/lib/Controller/ findMementos.php
103 lines
3.3 KiB

  1. <?php
  2. namespace OCA\Memento\Controller;
  3. use DOMDocument;
  4. use DateTime;
  5. // Finds HTML files that claim to be a snapshot of the given URL;
  6. // Returns an array of mementos, sorted by datetime, with each memento represented by an array:
  7. // [
  8. // 'mementoUrl' => URL of the file, relative to the nextcloud instance
  9. // 'originalUrl' => original URL, presumably equal to the given $url, except we normalise a bit
  10. // 'datetime' => snapshot datetime as a unix timestamp
  11. // ]
  12. function findMementos($folder, $url) {
  13. // Get all HTML files the user owns.
  14. $files = $folder->searchByMime('text/html');
  15. // Filter them for pages that have a <link rel="original"> referring to the given URL.
  16. $matchingMementos = array();
  17. foreach ($files as $file) {
  18. $content = $file->getContent();
  19. try {
  20. $DOM = new DOMDocument;
  21. $DOM->loadHTML($content);
  22. $headElement = $DOM->documentElement->getElementsByTagName('head')[0];
  23. $originalUrls = getOriginalUrls($headElement);
  24. foreach ($originalUrls as $originalUrl) {
  25. if (normaliseUrl($originalUrl) === normaliseUrl($url)) {
  26. // Found a match!
  27. // Read its datetime
  28. $datetime = getDatetime($headElement);
  29. // Construct its URL.
  30. $absoluteFilePath = $file->getPath();
  31. $relativeFilePath = $folder->getRelativePath($absoluteFilePath);
  32. $mementoUrl = joinPaths("/apps/raw/files", $relativeFilePath); // XXX hardcoded dependency
  33. $matchingMementos[] = [
  34. 'mementoUrl' => $mementoUrl,
  35. 'originalUrl' => $originalUrl,
  36. 'datetime' => $datetime
  37. ];
  38. }
  39. }
  40. } catch (Exception $e) {
  41. continue;
  42. }
  43. }
  44. // Sort mementos by their datetime. Oldest first.
  45. usort($matchingMementos, function ($m1, $m2) { return $m1['datetime'] <=> $m2['datetime']; });
  46. return $matchingMementos;
  47. }
  48. function joinPaths($piece1, $piece2) {
  49. $left = rtrim($piece1, '/');
  50. $right = ltrim($piece2, '/');
  51. return "$left/$right";
  52. }
  53. // Reads hrefs from any <link> with relation type "original".
  54. // (note the plural: we also accept pages that claim to correspond to multiple original URLs)
  55. function getOriginalUrls($headElement) {
  56. $originalUrls = [];
  57. $links = $headElement->getElementsByTagName('link');
  58. foreach ($links as $link) {
  59. $rels = explode(' ', $link->getAttribute('rel'));
  60. if (in_array('original', $rels)) {
  61. $href = $link->getAttribute('href');
  62. $href = filter_var($href, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED);
  63. if ($href) {
  64. $originalUrls[] = $href;
  65. }
  66. }
  67. }
  68. return $originalUrls;
  69. }
  70. // Read the content of the first <meta http-equiv="Memento-Datetime">, if any.
  71. function getDatetime($headElement) {
  72. $metas = $headElement->getElementsByTagName('meta');
  73. foreach($metas as $meta) {
  74. // Let's match case-insensitively, I guess?
  75. if (strtolower($meta->getAttribute('http-equiv')) === 'memento-datetime') {
  76. $datetime = $meta->getAttribute('content');
  77. $datetime = DateTime::createFromFormat(DateTime::RFC1123, $datetime)->getTimestamp();
  78. return $datetime; // Return directly at the first match
  79. }
  80. }
  81. return null;
  82. }
  83. function normaliseUrl($url) {
  84. // Ignore trailing slashes. Because everybody does.
  85. $url = rtrim($url, '/');
  86. // HACK. Replace multiple slashes with a single one. Because Nextcloud will have already done this
  87. // to the queried url (e.g. 'http://abc' arrives to us as 'http:/abc').
  88. $url = preg_replace('%/{2,}%', '/', $url);
  89. return $url;
  90. }