plugins = $plugins ?? new PluginDetector(); $this->themes = $themes ?? new ThemeDetector(); $this->builder = $builder; $this->connections = $connections ?? new ConnectionsResolver(); } public function registerRoutes(): void { register_rest_route( 'intellilanz/v1', '/cleanup/plugin', array( 'methods' => 'POST', 'callback' => array( $this, 'deletePlugin' ), 'permission_callback' => array( $this, 'pluginPermissionsCheck' ) ) ); register_rest_route( 'intellilanz/v1', '/cleanup/theme', array( 'methods' => 'POST', 'callback' => array( $this, 'deleteTheme' ), 'permission_callback' => array( $this, 'themePermissionsCheck' ) ) ); } public function pluginPermissionsCheck(): bool { return current_user_can( 'manage_options' ) && current_user_can( 'delete_plugins' ); } public function themePermissionsCheck(): bool { return current_user_can( 'manage_options' ) && current_user_can( 'delete_themes' ); } public function deletePlugin( $request ) { if ( ! $this->pluginPermissionsCheck() ) { return $this->forbidden(); } if ( ! wp_is_file_mod_allowed( 'intellilanz_cleanup' ) ) { return $this->error( 'intellilanz_file_mods_disabled', __( 'File modifications are disabled.', 'intellilanz' ), 403 ); } try { return $this->deletePluginRequest( $request ); } catch ( \Throwable $error ) { return $this->error( 'intellilanz_delete_failed', __( 'WordPress could not delete the plugin.', 'intellilanz' ), 500 ); } } private function deletePluginRequest( $request ) { if ( ! $this->confirmed( $request ) ) { return $this->error( 'intellilanz_confirmation_required', __( 'Deletion requires explicit confirmation.', 'intellilanz' ), 400 ); } $file = $this->identifier( $request, 'plugin_file' ); if ( $file === '' || validate_file( $file ) !== 0 ) { return $this->error( 'intellilanz_invalid_component', __( 'The requested plugin is invalid.', 'intellilanz' ), 400 ); } $node = $this->find( $this->plugins, 'file', $file ); $metadata = $node === null ? array() : (array) ( $node['metadata'] ?? array() ); if ( $node === null || ( $metadata['kind'] ?? null ) !== 'standard' || ! empty( $metadata['active'] ) || ! empty( $metadata['network_active'] ) || ! empty( $metadata['must_use'] ) ) { return $this->error( 'intellilanz_component_not_inactive', __( 'Only a currently inactive standard plugin can be deleted.', 'intellilanz' ), 409 ); } $related = $this->relatedComponents( $node['id'] ); if ( ! $this->filesystemDirect() ) { return $this->error( 'intellilanz_filesystem_credentials_required', __( 'WordPress requires filesystem credentials before this plugin can be deleted.', 'intellilanz' ), 409 ); } if ( ! function_exists( 'delete_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $result = delete_plugins( array( $file ) ); if ( $result !== true ) { return $this->error( 'intellilanz_delete_failed', __( 'WordPress could not delete the plugin.', 'intellilanz' ), 500 ); } return $this->response( 'plugin', $file, $related ); } public function deleteTheme( $request ) { if ( ! $this->themePermissionsCheck() ) { return $this->forbidden(); } if ( ! wp_is_file_mod_allowed( 'intellilanz_cleanup' ) ) { return $this->error( 'intellilanz_file_mods_disabled', __( 'File modifications are disabled.', 'intellilanz' ), 403 ); } try { return $this->deleteThemeRequest( $request ); } catch ( \Throwable $error ) { return $this->error( 'intellilanz_delete_failed', __( 'WordPress could not delete the theme.', 'intellilanz' ), 500 ); } } private function deleteThemeRequest( $request ) { if ( ! $this->confirmed( $request ) ) { return $this->error( 'intellilanz_confirmation_required', __( 'Deletion requires explicit confirmation.', 'intellilanz' ), 400 ); } $stylesheet = $this->identifier( $request, 'stylesheet' ); if ( $stylesheet === '' || validate_file( $stylesheet ) !== 0 ) { return $this->error( 'intellilanz_invalid_component', __( 'The requested theme is invalid.', 'intellilanz' ), 400 ); } $node = $this->find( $this->themes, 'stylesheet', $stylesheet ); $metadata = $node === null ? array() : (array) ( $node['metadata'] ?? array() ); if ( $node === null || ! empty( $metadata['active'] ) || ! empty( $metadata['is_active_parent'] ) ) { return $this->error( 'intellilanz_component_not_inactive', __( 'Only a currently inactive theme can be deleted.', 'intellilanz' ), 409 ); } $related = $this->relatedComponents( $node['id'] ); if ( ! $this->filesystemDirect() ) { return $this->error( 'intellilanz_filesystem_credentials_required', __( 'WordPress requires filesystem credentials before this theme can be deleted.', 'intellilanz' ), 409 ); } if ( ! function_exists( 'delete_theme' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } $result = delete_theme( $stylesheet ); if ( $result !== true ) { return $this->error( 'intellilanz_delete_failed', __( 'WordPress could not delete the theme.', 'intellilanz' ), 500 ); } return $this->response( 'theme', $stylesheet, $related ); } private function confirmed( $request ): bool { return is_object( $request ) && is_callable( array( $request, 'get_param' ) ) && $request->get_param( 'confirm' ) === true; } private function identifier( $request, string $key ): string { if ( ! is_object( $request ) || ! is_callable( array( $request, 'get_param' ) ) ) { return ''; } return str_replace( '\\', '/', sanitize_text_field( (string) $request->get_param( $key ) ) ); } private function find( DetectorInterface $detector, string $key, string $identifier ): ?array { $result = $detector->detect(); foreach ( is_array( $result['nodes'] ?? null ) ? $result['nodes'] : array() as $node ) { $data = $node->jsonSerialize(); if ( ( $data['metadata'][ $key ] ?? null ) === $identifier ) { return $data; } } return null; } private function filesystemDirect(): bool { if ( ! function_exists( 'get_filesystem_method' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } return get_filesystem_method() === 'direct'; } private function relatedComponents( string $node_id ): array { if ( $this->builder === null ) { return array(); } $data = $this->connections->resolve( $this->builder->build(), $node_id ); return array_map( static function ( array $item ): array { return array( 'id' => $item['node']['id'], 'label' => $item['node']['label'], 'type' => $item['node']['type'] ); }, $data['depended_on_by'] ?? array() ); } private function response( string $type, string $identifier, array $related = array() ): \WP_REST_Response { $data = array( 'deleted' => true, 'type' => $type, 'identifier' => $identifier ); if ( $related !== array() ) { $data['related_components'] = $related; } $response = new \WP_REST_Response( $data, 200 ); $response->header( 'Cache-Control', 'no-store, private' ); return $response; } private function forbidden(): \WP_Error { return $this->error( 'intellilanz_forbidden', __( 'You cannot delete this component.', 'intellilanz' ), rest_authorization_required_code() ); } private function error( string $code, string $message, int $status ): \WP_Error { return new \WP_Error( $code, $message, array( 'status' => $status ) ); } }