settings = $settings; $this->imports = $imports; } /** * Hook the routes. */ public function register(): void { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } /** * Register the routes. */ public function register_routes(): void { register_rest_route( self::NAMESPACE, self::ROUTE, array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); foreach ( array( 'apply', 'deactivate' ) as $action ) { register_rest_route( self::NAMESPACE, self::ROUTE . '/' . $action, array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, $action ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'source' => array( 'type' => 'string', 'required' => true, ), ), ) ); } register_rest_route( self::NAMESPACE, self::ROUTE . '/undo', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'undo' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); } /** * Whether the current user may manage the plugin. */ public function can_manage(): bool { return current_user_can( Capabilities::MANAGE ); } /** * The sources that were found and the last import's snapshot. */ public function get(): WP_REST_Response { return new WP_REST_Response( $this->payload() ); } /** * Import one source. * * @param WP_REST_Request $request Request with the `source` id. * @return WP_REST_Response|WP_Error */ public function apply( WP_REST_Request $request ) { $importer = $this->importer( $request ); if ( $importer instanceof WP_Error ) { return $importer; } try { $result = $this->imports->apply( $importer ); } catch ( InvalidSettingsException $e ) { return new WP_Error( 'consentfold_invalid_settings', __( 'The imported settings are not valid, so nothing was imported.', 'consentfold' ), array( 'status' => 400, 'errors' => $e->errors(), ) ); } if ( null === $result ) { return self::not_found(); } return new WP_REST_Response( $this->payload() + array( 'applied' => array( 'changed' => count( $result['changed'] ), 'locked' => $result['locked'], ), ) ); } /** * Restore the settings the last import replaced. * * @return WP_REST_Response|WP_Error */ public function undo() { try { $undone = $this->imports->undo(); } catch ( InvalidSettingsException $e ) { return new WP_Error( 'consentfold_invalid_settings', __( 'The earlier settings are not valid any more, so the import was not undone.', 'consentfold' ), array( 'status' => 400, 'errors' => $e->errors(), ) ); } if ( ! $undone ) { return new WP_Error( 'consentfold_nothing_to_undo', __( 'There is no import to undo.', 'consentfold' ), array( 'status' => 409 ) ); } return new WP_REST_Response( $this->payload() ); } /** * Deactivate the other plugin of a source. * * @param WP_REST_Request $request Request with the `source` id. * @return WP_REST_Response|WP_Error */ public function deactivate( WP_REST_Request $request ) { $importer = $this->importer( $request ); if ( $importer instanceof WP_Error ) { return $importer; } self::load_plugin_functions(); if ( ! self::may_deactivate( $importer->plugin_file() ) ) { return new WP_Error( 'consentfold_cannot_deactivate', __( 'You are not allowed to deactivate this plugin.', 'consentfold' ), array( 'status' => 403 ) ); } if ( ! is_plugin_active( $importer->plugin_file() ) && ! is_plugin_active_for_network( $importer->plugin_file() ) ) { return new WP_Error( 'consentfold_plugin_not_active', __( 'That plugin is not active.', 'consentfold' ), array( 'status' => 409 ) ); } // A network-active plugin is switched off for the whole network, which only a network administrator may do. deactivate_plugins( $importer->plugin_file(), false, is_plugin_active_for_network( $importer->plugin_file() ) ); return new WP_REST_Response( $this->payload() ); } /** * The importer a request names. * * @param WP_REST_Request $request Request with the `source` id. * @return Importer|WP_Error */ private function importer( WP_REST_Request $request ) { $importer = $this->imports->importers()[ (string) $request->get_param( 'source' ) ] ?? null; return $importer ?? self::not_found(); } /** * Error for a source that is unknown or left no data. */ private static function not_found(): WP_Error { return new WP_Error( 'consentfold_no_import_source', __( 'There is nothing to import from that plugin.', 'consentfold' ), array( 'status' => 404 ) ); } /** * Whether the current user may deactivate a plugin: the right to deactivate it and, when it is network active, * the right to manage network plugins too. * * @param string $file Plugin file, relative to the plugins directory. */ private static function may_deactivate( string $file ): bool { self::load_plugin_functions(); if ( is_multisite() && is_plugin_active_for_network( $file ) && ! current_user_can( 'manage_network_plugins' ) ) { return false; } return current_user_can( 'deactivate_plugin', $file ); } /** * The plugin functions, which core loads in the admin only. */ private static function load_plugin_functions(): void { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } } /** * What the tab shows. * * @return array{sources: list>, snapshot: array{source: string, name: string, at: int, changed: int, imports: list}|null, locked: list} */ private function payload(): array { self::load_plugin_functions(); $sources = array(); foreach ( $this->imports->importers() as $importer ) { $preview = $this->imports->preview( $importer ); if ( null === $preview ) { continue; } $file = $importer->plugin_file(); $sources[] = array( 'id' => $importer->id(), 'name' => $importer->name(), 'plugin_file' => $file, 'active' => is_plugin_active( $file ) || is_plugin_active_for_network( $file ), 'can_deactivate' => self::may_deactivate( $file ), ) + $preview; } return array( 'sources' => $sources, 'snapshot' => $this->imports->snapshot(), 'locked' => $this->settings->locked_keys(), ); } }