registry = $registry; } /** * Hook route registration. * * @since 1.0.0 * @return void */ public function register() { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } /** * Register routes. * * @since 1.0.0 * @return void */ public function register_routes() { register_rest_route( self::NAMESPACE, '/modules', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'list_modules' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); register_rest_route( self::NAMESPACE, '/lookup', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'lookup' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'source' => array( 'type' => 'string', 'required' => true, ), 'q' => array( 'type' => 'string', 'default' => '', ), ), ) ); register_rest_route( self::NAMESPACE, '/system', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'refresh_system' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); register_rest_route( self::NAMESPACE, '/preferences', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_preferences' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'theme' => array( 'type' => 'string', 'enum' => array( 'system', 'light', 'dark' ), 'required' => true, ), ), ) ); register_rest_route( self::NAMESPACE, '/modules/(?P[a-z0-9-]+)/data/(?P[a-z0-9_-]+)', array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_data' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'confirm' => array( 'type' => 'string', 'default' => '', ), ), ) ); register_rest_route( self::NAMESPACE, '/keep-data', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_keep_data' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'keep' => array( 'type' => 'boolean', 'required' => true, ), ), ) ); register_rest_route( self::NAMESPACE, '/modules/(?P[a-z0-9-]+)', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_module' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'enabled' => array( 'type' => 'boolean', 'required' => false, ), 'settings' => array( 'type' => 'object', 'required' => false, ), ), ) ); } /** * Only store managers may manage power-ups. * * @since 1.0.0 * @return bool */ public function can_manage() { return current_user_can( 'manage_woocommerce' ); } /** * POST /preferences: the current user's panel theme. * * @since 1.0.0 * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public function update_preferences( WP_REST_Request $request ) { update_user_meta( get_current_user_id(), AdminPage::THEME_META, (string) $request['theme'] ); return rest_ensure_response( array( 'theme' => (string) $request['theme'] ) ); } /** * DELETE /modules/{id}/data/{item}: delete one batch of a power-up's data. Answers how many * are left (the Data section repeats until 0) and the redrawn section. * * @since 1.0.0 * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function delete_data( WP_REST_Request $request ) { $module = $this->accessible_module( (string) $request['id'] ); if ( is_wp_error( $module ) ) { return $module; } $items = $module->data_items(); $item = $items[ (string) $request['item'] ] ?? null; if ( ! $item || ! $item['delete'] ) { return new WP_Error( 'apwc_no_data', __( 'There’s nothing to delete here.', 'admin-powerups-for-woocommerce' ), array( 'status' => 404 ) ); } if ( $item['confirm'] ) { // Same rule as the panel's button unlock: any case, in any language. $confirmed = DevGuard::check_confirm( $request, (string) $item['confirm'] ); if ( is_wp_error( $confirmed ) ) { return $confirmed; } } $left = max( 0, (int) call_user_func( $item['delete'] ) ); if ( ! $left ) { /** * Fires after a power-up's data item was fully deleted from its Data section. * * @since 1.0.0 * @param string $module_id Module ID. * @param string $item_id Item ID. */ do_action( 'apwc_module_data_deleted', $module->id(), $item['id'] ); } return rest_ensure_response( array( 'left' => $left, 'html' => $left ? '' : \SmartWP\AdminPowerups\Admin\DataPanel::html( $module ), ) ); } /** * POST /keep-data: keep or remove this site's data when the plugin is deleted. Switching it * off (data deleted with the plugin) needs the typed word (`confirm`), checked here on the * server, not only in the panel. * * @since 1.0.0 * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function update_keep_data( WP_REST_Request $request ) { if ( ! $request['keep'] && $this->registry->store()->keep_data() ) { $confirmed = DevGuard::check_confirm( $request, DevGuard::confirm_word( 'delete' ) ); if ( is_wp_error( $confirmed ) ) { return $confirmed; } } $this->registry->store()->set_keep_data( (bool) $request['keep'] ); return rest_ensure_response( array( 'keep' => (bool) $request['keep'] ) ); } /** * GET /lookup: search a source for the `search` settings field. * * @since 1.0.0 * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function lookup( WP_REST_Request $request ) { $source = sanitize_key( (string) $request['source'] ); $sources = Lookup::sources(); if ( ! isset( $sources[ $source ] ) ) { return new WP_Error( 'apwc_unknown_source', __( 'Unknown search source.', 'admin-powerups-for-woocommerce' ), array( 'status' => 400 ) ); } // A source may need more than the panel's manage_woocommerce (e.g. one listing options). // A `capability` that is set but isn't a usable capability name fails closed. if ( is_array( $sources[ $source ] ) && array_key_exists( 'capability', $sources[ $source ] ) ) { $cap = $sources[ $source ]['capability']; $ok = is_string( $cap ) && '' !== trim( $cap ) && current_user_can( trim( $cap ) ); } else { $ok = true; } if ( ! $ok ) { return new WP_Error( 'apwc_lookup_forbidden', __( 'Sorry, you are not allowed to search this.', 'admin-powerups-for-woocommerce' ), array( 'status' => 403 ) ); } return rest_ensure_response( Lookup::search( $source, sanitize_text_field( (string) $request['q'] ) ) ); } /** * POST /system: re-check everything in the sidebar System card and return the redrawn card. * * @since 1.0.0 * @return WP_REST_Response */ public function refresh_system() { ob_start(); \SmartWP\AdminPowerups\Admin\Sidebar::render_system_card( $this->registry, true ); $status = \SmartWP\AdminPowerups\Support\SystemStatus::get(); return rest_ensure_response( array( 'html' => ob_get_clean(), 'status' => $status['jobs']['status'], ) ); } /** * GET /modules. * * @since 1.0.0 * @return WP_REST_Response */ public function list_modules() { $data = array(); foreach ( $this->registry->visible() as $module ) { $data[] = $module->to_array(); } return rest_ensure_response( $data ); } /** * A power-up the current user may see, or 404 `apwc_unknown_module` (also for admin-only * power-ups asked for by a store manager, so their IDs aren't confirmed). * * @since 1.0.0 * @param string $id Module ID. * @return \SmartWP\AdminPowerups\Modules\AbstractModule|WP_Error */ private function accessible_module( $id ) { $module = $this->registry->get( $id ); if ( ! $module || ! $module->user_can_access() ) { return new WP_Error( 'apwc_unknown_module', __( 'That power-up does not exist.', 'admin-powerups-for-woocommerce' ), array( 'status' => 404 ) ); } return $module; } /** * POST /modules/{id}. * * @since 1.0.0 * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function update_module( WP_REST_Request $request ) { // Before anything is saved or switched: a power-up this user may not see doesn't exist for them. $module = $this->accessible_module( (string) $request['id'] ); if ( is_wp_error( $module ) ) { return $module; } // Check the switch first: a refused request must not save settings on the way out. $enabled = $request->has_param( 'enabled' ) ? (bool) $request->get_param( 'enabled' ) : null; if ( true === $enabled && '' !== $module->unmet_requirement() ) { return new WP_Error( 'apwc_unmet_requirement', $module->unmet_requirement(), array( 'status' => 409 ) ); } if ( $request->has_param( 'settings' ) ) { $settings = $request->get_param( 'settings' ); $module->save_settings( is_array( $settings ) ? $settings : array() ); } if ( null !== $enabled ) { $this->registry->set_enabled( $module->id(), $enabled ); } return rest_ensure_response( $module->to_array() ); } }