|null */ public function lastResult(): ?array { $last = Config::get(self::RESULT_KEY); return is_array($last) ? $last : null; } /** @return array */ public function run(): array { $uploads = (string) (wp_upload_dir(null, false)['basedir'] ?? ''); $roots = [ WP_PLUGIN_DIR => false, (string) get_theme_root() => false, WPMU_PLUGIN_DIR => false, ]; if ($uploads !== '') { $roots[$uploads] = true; } // The agent itself and its own folders (logs, encrypted archives), in either // edition's layout: nothing to analyse there. $skip = array_merge( [ untrailingslashit(wp_normalize_path(SENTINELO_PLUGIN_DIR)), WP_CONTENT_DIR . '/cache', ], sentinelo_storage_roots() ); $result = ($this->scanner ?? new SignatureScanner())->scan($roots, $skip, ABSPATH); $result['scanned_at'] = gmdate('c'); Config::set(self::RESULT_KEY, $result); return $result; } }