group => absolute folder without a trailing slash */ public static function roots(?Scope $scope = null): array { $uploads = wp_upload_dir(null, false); $roots = [ 'themes' => get_theme_root(), 'plugins' => WP_PLUGIN_DIR, 'mu-plugins' => WPMU_PLUGIN_DIR, 'uploads' => $scope !== null && $scope->is_subsite() ? $scope->uploads_dir : $uploads['basedir'], 'other' => WP_CONTENT_DIR, ]; /** Lets tests (and unusual hosts) point the groups at other folders. */ $roots = (array) apply_filters('relay_backup_roots', $roots); return array_map(static fn ($path) => rtrim(wp_normalize_path((string) $path), '/'), $roots); } /** * Relay's own plugin folder name, never backed up, copied or overwritten. Taken from plugin_basename(), * which knows the name under wp-content/plugins even when that folder is a symlink (PHP's __FILE__ is * the resolved path there). */ public static function relay_dir(): string { $basename = plugin_basename(RELAY_FILE); return substr_count($basename, '/') === 1 ? dirname($basename) : basename(dirname(RELAY_FILE)); } /** * What each group leaves out: Relay's own items (always), the built-in rules, the user's rules and, per * group, top-level items that were not chosen. * * @param string[] $user user rules (spec ยง5.1), "!rule" re-includes * @param array $unchosen group => top-level names to leave out * @return array */ public static function excludes(array $roots, array $user = [], array $unchosen = []): array { $own = [ 'themes' => [], 'plugins' => [self::relay_dir()], 'mu-plugins' => [CompatPlugin::FILE], 'uploads' => Paths::NAMES, 'other' => self::other_top_level($roots), ]; $out = []; foreach ($own as $group => $names) { $out[$group] = new Excludes(in_array($group, ['uploads', 'other'], true), array_merge($names, $unchosen[$group] ?? []), $user, $group === 'other' ? '' : $group . '/'); } return $out; } /** "group/top-level" items a backup archive or another site may never write (see EntryName::parse(), FileList::import()). */ public static function protected_items(array $roots): array { $items = ['plugins/' . self::relay_dir(), 'mu-plugins/' . CompatPlugin::FILE, 'uploads/siterelay', 'uploads/relay']; foreach (self::other_top_level($roots) as $name) { $items[] = 'other/' . $name; } foreach (RootFiles::never($roots) as $name) { $items[] = 'root/' . $name; } return $items; } /** @return string[] top-level wp-content names excluded from "other": the other four roots, caches, drop-ins */ public static function other_top_level(array $roots): array { $names = self::NOT_OTHER; foreach (['themes', 'plugins', 'mu-plugins', 'uploads'] as $group) { // The wp-content folder that holds the group, also when the group lives deeper (a subsite's uploads/sites/N). if (strpos($roots[$group], $roots['other'] . '/') === 0) { $names[] = explode('/', substr($roots[$group], strlen($roots['other']) + 1))[0]; } } return array_values(array_unique($names)); } }