globs = array_merge( self::defaults(), $clean ); $this->skip_groups = array_values( array_intersect( self::GROUPS, array_map( 'strval', $skip_groups ) ) ); } /** The default exclude list (spec 4.6). Root-anchored entries start with "/". */ public static function defaults(): array { return [ '/debug.log', '/*.log', '/cache/', '/upgrade/', '/wflogs/', '/ai1wm-backups/', '/updraft/', '/backups-dup-*', '/duplicator-backups/', '/wpvividbackups/', '/mench-migrate-*/', 'node_modules/', '.git/', '.DS_Store', ]; } /** Whether the relative path (from wp-content, "/" separated) must be left out of the backup. */ public function is_excluded( string $relative_path, bool $is_dir ): bool { $path = trim( str_replace( '\\', '/', $relative_path ), '/' ); if ( '' === $path ) { return false; } $segments = explode( '/', $path ); if ( $this->skip_groups && $this->group_skipped( $segments[0], 1 === count( $segments ) ? $is_dir : true ) ) { return true; } $prefix = ''; $last = count( $segments ) - 1; foreach ( $segments as $i => $segment ) { $prefix .= ( '' === $prefix ? '' : '/' ) . $segment; $dir = $i < $last || $is_dir; // Every parent is a directory. foreach ( $this->globs as $glob ) { if ( $this->matches( $glob, $segment, $prefix, $dir ) ) { return true; } } } return false; } private function group_skipped( string $top, bool $is_dir ): bool { $named = [ 'uploads', 'plugins', 'themes', 'mu-plugins' ]; if ( in_array( $top, $named, true ) ) { return $is_dir && in_array( $top, $this->skip_groups, true ); } return in_array( 'others', $this->skip_groups, true ); } private function matches( string $glob, string $segment, string $path, bool $is_dir ): bool { if ( '/' === substr( $glob, -1 ) ) { if ( ! $is_dir ) { return false; } $glob = rtrim( $glob, '/' ); } if ( '' === $glob || '/' === $glob ) { return false; // "/" or "//" trims to nothing: it names no path (and $glob[0] would warn). } if ( '/' === $glob[0] ) { return fnmatch( substr( $glob, 1 ), $path, FNM_PATHNAME ); } if ( false === strpos( $glob, '/' ) ) { return fnmatch( $glob, $segment, FNM_PATHNAME ); } return fnmatch( $glob, $path, FNM_PATHNAME ); } }