PicoZCache.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. <?php
  2. /**
  3. * Pico Cache plugin
  4. * Name "PicoZCache" is to be loaded as last.
  5. *
  6. * Author Maximilian Beck before 2.0, Nepose since 2.0
  7. * @author Improvements by various authors, gathered by https://github.com/ohnonot (2022)
  8. * PME20240329 Cache invalidate on changed source
  9. * @license http://opensource.org/licenses/MIT
  10. * @version 2.0
  11. */
  12. class PicoZCache extends AbstractPicoPlugin
  13. {
  14. const API_VERSION=3;
  15. protected $dependsOn = array();
  16. private $doCache = true;
  17. private $FileName;
  18. private $url;
  19. protected $enabled = false;
  20. public function onConfigLoaded(array &$config)
  21. {
  22. // ensure cache_dir ends with '/'
  23. $this->Dir = rtrim($this->getPluginConfig('dir', 'content/zcache/','content/zcache/'),'/').'/';
  24. $this->Time = $this->getPluginConfig('expires', 0);
  25. $this->XHTML = $this->getPluginConfig('xhtml_output', false);
  26. $this->Exclude = $this->getPluginConfig('exclude', null);
  27. $this->ExcludeRegex = $this->getPluginConfig('exclude_regex', null);
  28. $this->IgnoreQuery = $this->getPluginConfig('ignore_query', false);
  29. $this->IgnoreQueryExclude = $this->getPluginConfig('ignore_query_exclude', null);
  30. $this->Invalidate = $this->getPluginConfig('invalidate', false);
  31. }
  32. public function onRequestUrl(&$url)
  33. {
  34. $this->url = $url;
  35. }
  36. public function onRequestFile(&$file)
  37. {
  38. $name = $this->url == "" ? "index" : $this->url;
  39. //~ echo 'File: '. $file . '<br/>Url: '. $name ;
  40. // Skip cache for url matching an excluded page
  41. if ($this->Exclude && in_array($name, $this->Exclude)) {
  42. $this->doCache = false;
  43. return;
  44. }
  45. // Skip cache for url matching exclude regex
  46. //PME20240336 Regex needs '/' delimiters.
  47. if ($this->ExcludeRegex && preg_match('/' . preg_quote($this->ExcludeRegex, '/') . '/', $name)) {
  48. $this->doCache = false;
  49. return;
  50. }
  51. // Add query to name if so configured
  52. if ($this->IgnoreQuery === false || in_array($name, $this->IgnoreQueryExclude)) {
  53. $query = (!empty($_GET)) ? '__'.md5(serialize($_GET)) : null;
  54. $name = $name . $query;
  55. }
  56. //PME20230326 replace every path delimiter with a '+' to prevent cache clashes.
  57. // Replace any character except numbers and digits with a '-' to form valid file names
  58. $seq = explode('/', $name);
  59. $seq = array_map(fn($x): string => preg_replace('/[^A-Za-z0-9_\-]/', '_', $x), $seq);
  60. $this->FileName = $this->Dir . implode('+', $seq) . '.html';
  61. // If invalidate cached file when source page has changed, delete stale
  62. // cached copy when source page is more recent
  63. if (file_exists($this->FileName) && $this->Invalidate && filemtime($file) > filemtime($this->FileName)) {
  64. // Delete stale cache copy
  65. // echo 'filemtime(source) = ' . filemtime($file) . '<br/>';
  66. // echo 'filemtime(cached) = ' . filemtime($this->FileName).'<br/>';
  67. // echo 'time = '. time().'<br/>';
  68. // echo 'this->Time = ' . $this->Time.'<br/>';
  69. unlink($this->FileName);
  70. }
  71. // If a cached file exists and the cacheTime is not expired, load the file and exit
  72. if (file_exists($this->FileName)) {
  73. if ($this->Time > 0) {
  74. //~ echo time().'<br/>' . filemtime($this->FileName).'<br/>' . $this->Time.'<br/>';
  75. header("Expires: " . gmdate("D, d M Y H:i:s", $this->Time + filemtime($this->FileName)) . " GMT");
  76. ($this->XHTML) ? header('Content-Type: application/xhtml+xml') : header('Content-Type: text/html');
  77. if (time() - filemtime($this->FileName) > $this->Time) return;
  78. }
  79. die(readfile($this->FileName));
  80. }
  81. }
  82. public function on404ContentLoaded(&$rawContent)
  83. {
  84. //don't cache error pages. This prevents filling up the cache with non existent pages
  85. $this->doCache = false;
  86. }
  87. public function onPageRendered(&$output)
  88. {
  89. if ($this->doCache) {
  90. if (!is_dir($this->Dir)) {
  91. mkdir($this->Dir, 0755, true);
  92. }
  93. file_put_contents($this->FileName, $output);
  94. }
  95. }
  96. }