1<?php 2/** 3 * DokuWiki Plugin pagestats (Helper Component) 4 * Common functionality for page stats calculation 5 */ 6 7if (!defined('DOKU_INC')) die(); 8 9class helper_plugin_pagestats extends DokuWiki_Plugin { 10 private $cache = null; 11 private $cacheTime = 3600; // Cache standardmäßig für 1 Stunde 12 13 /** 14 * Constructor - reads configuration 15 */ 16 public function __construct() { 17 // Get cache lifetime from configuration (if set) 18 $cacheTime = $this->getConf('cacheTime'); 19 if (is_numeric($cacheTime)) { 20 $this->cacheTime = (int)$cacheTime; 21 } 22 23 // Wenn die Cache-Zeit geändert wurde, Cache löschen 24 $optionChanged = false; 25 if (isset($_POST['config']['plugin']['pagestats']['cacheTime']) && 26 $_POST['config']['plugin']['pagestats']['cacheTime'] != $this->cacheTime) { 27 $optionChanged = true; 28 } 29 30 // Wenn Namensräume ausschließen geändert wurde, Cache löschen 31 if (isset($_POST['config']['plugin']['pagestats']['excludeNamespaces'])) { 32 $optionChanged = true; 33 } 34 35 if ($optionChanged) { 36 $this->clearCache(); 37 } 38 } 39 40 /** 41 * Calculate all stats at once to avoid multiple directory scans 42 * 43 * @return array Associative array with all statistics 44 */ 45 public function getStats() { 46 // Check if we have cached values 47 $cache = $this->loadCache(); 48 if ($cache !== null) { 49 return $cache; 50 } 51 52 // Calculate all stats 53 try { 54 $dataPathPages = DOKU_INC . 'data/pages'; 55 $dataPathMedia = DOKU_INC . 'data/media'; 56 57 $excludeNamespaces = array_map('trim', explode(',', $this->getConf('excludeNamespaces'))); 58 59 $stats = [ 60 'PAGESTATSPAGE' => 0, 61 'PAGESTATSMB' => 0, 62 'MEDIASTATSPAGE' => 0, 63 'MEDIASTATSMB' => 0 64 ]; 65 66 // Pages stats 67 list($count, $size) = $this->calculateStats($dataPathPages, 'txt', $excludeNamespaces); 68 $stats['PAGESTATSPAGE'] = $count; 69 $stats['PAGESTATSMB'] = round($size / (1024 * 1024), 2); 70 71 // Media stats 72 list($count, $size) = $this->calculateStats($dataPathMedia, '', $excludeNamespaces); 73 $stats['MEDIASTATSPAGE'] = $count; 74 $stats['MEDIASTATSMB'] = round($size / (1024 * 1024), 2); 75 76 // Cache the results 77 $this->saveCache($stats); 78 79 return $stats; 80 } catch (Exception $e) { 81 $this->log('pagestats', 'Error calculating stats: '.$e->getMessage(), DOKU_INC.'pagestats_error.log'); 82 return [ 83 'PAGESTATSPAGE' => 0, 84 'PAGESTATSMB' => 0, 85 'MEDIASTATSPAGE' => 0, 86 'MEDIASTATSMB' => 0 87 ]; 88 } 89 } 90 91 /** 92 * Calculate both count and size in one iteration 93 * 94 * @param string $path Directory path 95 * @param string $extension File extension to filter, empty for all 96 * @param array $excludeNamespaces Namespaces to exclude 97 * @return array [count, size] 98 */ 99 private function calculateStats($path, $extension, $excludeNamespaces = []) { 100 if (!is_dir($path)) return [0, 0]; 101 102 $count = 0; 103 $totalSize = 0; 104 105 try { 106 $iterator = new RecursiveIteratorIterator( 107 new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS) 108 ); 109 110 foreach ($iterator as $file) { 111 // Skip if not a file or doesn't match extension 112 if (!$file->isFile() || ($extension !== '' && $file->getExtension() !== $extension)) { 113 continue; 114 } 115 116 // Skip if in excluded namespace 117 $relativePath = str_replace($path, '', $file->getPathname()); 118 $shouldExclude = false; 119 120 foreach ($excludeNamespaces as $ns) { 121 if (empty($ns)) continue; 122 if (strpos($relativePath, '/'.$ns.'/') !== false) { 123 $shouldExclude = true; 124 break; 125 } 126 } 127 128 if ($shouldExclude) continue; 129 130 // Count and add size 131 $count++; 132 $totalSize += $file->getSize(); 133 } 134 } catch (Exception $e) { 135 // Log error but continue with what we have 136 $this->log('pagestats', 'Error scanning directory: '.$e->getMessage(), DOKU_INC.'pagestats_error.log'); 137 } 138 139 return [$count, $totalSize]; 140 } 141 142 /** 143 * Save stats to cache 144 * 145 * @param array $stats The stats to cache 146 */ 147 private function saveCache($stats) { 148 if ($this->cacheTime <= 0) return; // Caching disabled 149 150 $cacheFile = $this->getCacheFilename(); 151 $data = [ 152 'time' => time(), 153 'stats' => $stats 154 ]; 155 156 io_saveFile($cacheFile, serialize($data)); 157 } 158 159 /** 160 * Load stats from cache if available and not expired 161 * 162 * @return array|null Stats or null if cache invalid/expired 163 */ 164 private function loadCache() { 165 if ($this->cacheTime <= 0) return null; // Caching disabled 166 if ($this->cache !== null) return $this->cache; // Already loaded 167 168 $cacheFile = $this->getCacheFilename(); 169 170 if (!file_exists($cacheFile)) return null; 171 172 $data = unserialize(io_readFile($cacheFile, false)); 173 174 if (!$data || !isset($data['time']) || !isset($data['stats'])) { 175 return null; 176 } 177 178 // Check if cache expired 179 if (time() - $data['time'] > $this->cacheTime) { 180 return null; 181 } 182 183 $this->cache = $data['stats']; 184 return $this->cache; 185 } 186 187 /** 188 * Get the cache filename 189 * 190 * @return string Full path to cache file 191 */ 192 private function getCacheFilename() { 193 return DOKU_INC . 'data/cache/pagestats.cache'; 194 } 195 196 /** 197 * Simple logging function 198 * 199 * @param string $plugin Plugin name 200 * @param string $message Log message 201 * @param string $file Log file 202 */ 203 private function log($plugin, $message, $file) { 204 $time = date('Y-m-d H:i:s'); 205 $logline = "[$time] [$plugin] $message\n"; 206 io_saveFile($file, $logline, true); 207 } 208 209 /** 210 * Clear the cache (e.g. if called from admin) 211 */ 212 public function clearCache() { 213 $cacheFile = $this->getCacheFilename(); 214 if (file_exists($cacheFile)) { 215 @unlink($cacheFile); 216 } 217 $this->cache = null; 218 } 219}