getDatabase(); $prefix = $db->getPrefix(); $db->setQuery('SHOW TABLE STATUS'); $tables = $db->loadObjectList() ?: []; $results = []; $totalSize = 0; $totalOverhead = 0; foreach ($tables as $t) { $sizeMb = round(($t->Data_length + $t->Index_length) / 1048576, 2); $overheadKb = round(($t->Data_free ?? 0) / 1024, 1); $totalSize += $sizeMb; $totalOverhead += $overheadKb; $results[] = (object) [ 'name' => $t->Name, 'rows' => (int) $t->Rows, 'engine' => $t->Engine, 'size_mb' => $sizeMb, 'overhead_kb' => $overheadKb, 'is_moko' => str_contains($t->Name, 'mokosuiteclient'), ]; } usort($results, fn($a, $b) => $b->size_mb <=> $a->size_mb); return ['tables' => $results, 'total_size_mb' => round($totalSize, 2), 'total_overhead_kb' => round($totalOverhead, 1), 'count' => \count($results)]; } /** * Optimize all tables or specific ones. */ public function optimizeTables(array $tableNames = []): array { $db = $this->getDatabase(); $count = 0; try { if (empty($tableNames)) { $db->setQuery('SHOW TABLE STATUS WHERE Data_free > 0'); $tables = $db->loadObjectList() ?: []; $tableNames = array_column($tables, 'Name'); } foreach ($tableNames as $name) { $db->setQuery('OPTIMIZE TABLE ' . $db->quoteName($name)); $db->execute(); $count++; } return ['success' => true, 'message' => "Optimized {$count} tables."]; } catch (\Throwable $e) { return ['success' => false, 'message' => 'Optimize failed: ' . $e->getMessage()]; } } /** * Repair all tables. */ public function repairTables(): array { $db = $this->getDatabase(); try { $db->setQuery('SHOW TABLE STATUS'); $tables = $db->loadObjectList() ?: []; $count = 0; foreach ($tables as $t) { if ($t->Engine === 'InnoDB' || $t->Engine === 'MyISAM') { $db->setQuery('REPAIR TABLE ' . $db->quoteName($t->Name)); $db->execute(); $count++; } } return ['success' => true, 'message' => "Repaired {$count} tables."]; } catch (\Throwable $e) { return ['success' => false, 'message' => 'Repair failed: ' . $e->getMessage()]; } } /** * Purge expired sessions. */ public function purgeSessions(): array { try { $db = $this->getDatabase(); $db->setQuery( $db->getQuery(true) ->delete($db->quoteName('#__session')) ->where($db->quoteName('time') . ' < ' . (time() - 86400)) )->execute(); return ['success' => true, 'message' => 'Expired sessions purged. ' . $db->getAffectedRows() . ' removed.']; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage()]; } } // ================================================================== // Temp/Cache Cleanup (#128) // ================================================================== /** * Get directory sizes for cleanup. */ public function getCleanupInfo(): array { $dirs = [ ['path' => JPATH_ROOT . '/cache', 'label' => 'Site Cache'], ['path' => JPATH_ADMINISTRATOR . '/cache', 'label' => 'Admin Cache'], ['path' => JPATH_ROOT . '/tmp', 'label' => 'Temp Directory'], ['path' => JPATH_ADMINISTRATOR . '/logs', 'label' => 'Log Files'], ]; $results = []; foreach ($dirs as $dir) { $size = 0; $files = 0; if (is_dir($dir['path'])) { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir['path'], \RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if ($file->isFile()) { $size += $file->getSize(); $files++; } } } $results[] = (object) [ 'label' => $dir['label'], 'path' => $dir['path'], 'size_mb' => round($size / 1048576, 2), 'files' => $files, 'writable' => is_writable($dir['path']), ]; } return $results; } /** * Clean a specific directory. */ public function cleanDirectory(string $dirKey): array { $allowed = [ 'site_cache' => JPATH_ROOT . '/cache', 'admin_cache' => JPATH_ADMINISTRATOR . '/cache', 'tmp' => JPATH_ROOT . '/tmp', 'logs' => JPATH_ADMINISTRATOR . '/logs', ]; if (!isset($allowed[$dirKey])) { return ['success' => false, 'message' => 'Invalid directory.']; } $dir = $allowed[$dirKey]; if (!is_dir($dir)) { return ['success' => false, 'message' => 'Directory not found.']; } $count = 0; try { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $item) { // Keep index.html and .htaccess files $name = $item->getFilename(); if ($name === 'index.html' || $name === '.htaccess') { continue; } if ($item->isDir()) { @rmdir($item->getPathname()); } else { @unlink($item->getPathname()); $count++; } } // Also clear opcache if (\function_exists('opcache_reset')) { \opcache_reset(); } return ['success' => true, 'message' => "Cleaned {$count} files from {$dirKey}."]; } catch (\Throwable $e) { return ['success' => false, 'message' => 'Cleanup failed: ' . $e->getMessage()]; } } }