rest_authorization_required_code() ) ); } return true; } /** * Admin-only gate for full-site, settings and rollback routes. * * The configurable capability may be an editor's; these routes can * replace the whole site or widen access, so they never follow it. * * @param WP_REST_Request $request The request. * @return true|WP_Error */ public function admin_permission_check( WP_REST_Request $request ) { unset( $request ); if ( ! current_user_can( 'manage_options' ) ) { return new WP_Error( 'transtrix_forbidden', __( 'Only an administrator can do this.', 'transtrix' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Full-site gate: an administrator, or a super admin on multisite. * * @param WP_REST_Request $request The request. * @return true|WP_Error */ public function fullsite_permission_check( WP_REST_Request $request ) { $allowed = $this->admin_permission_check( $request ); if ( true === $allowed && is_multisite() && ! current_user_can( 'manage_network_options' ) ) { return new WP_Error( 'transtrix_forbidden', __( 'Only a network administrator can do this.', 'transtrix' ), array( 'status' => rest_authorization_required_code() ) ); } return $allowed; } /** * Whether the current user passes the full-site gate. * * For routes open to the configurable capability that can still reach a * whole-site job or package: a whole-site package holds the user table, * so it must never follow an editor's capability. * * @return bool */ protected function can_access_full_site(): bool { return current_user_can( 'manage_options' ) && ( ! is_multisite() || current_user_can( 'manage_network_options' ) ); } /** * Whether the current user may export or list one of a module's types. * * The plugin's capability may be an editor's. Each post type is held to the * capability its own screens ask for, so an editor cannot export orders they * could not open. Payload types (orders, customers) are site-wide data, an * administrator's only, as they are on import. * * @param Module $module The module the type belongs to. * @param string $type Post type or payload type slug. * @return bool */ public function can_export_type( Module $module, string $type ): bool { if ( current_user_can( 'manage_options' ) ) { return true; } if ( array_key_exists( $type, $module->payload_types() ) ) { return false; } $object = get_post_type_object( $type ); return null !== $object && current_user_can( $object->cap->edit_others_posts ); } /** * The error for a content type the current user may not export. * * @return WP_Error */ protected function type_forbidden(): WP_Error { return new WP_Error( 'transtrix_forbidden', __( 'You don’t have permission to export this content type. Ask an administrator for access.', 'transtrix' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Whether the current user may reach this job. * * The plugin's capability may be an editor's. An administrator's export can * hold orders and customers an editor may not export, so below * manage_options only the user's own runs are reachable. * * @param array $record Job record. * @return bool */ protected function owns_job( array $record ): bool { return current_user_can( 'manage_options' ) || get_current_user_id() === (int) ( $record['user_id'] ?? 0 ); } /** * Whether the current user may download, import or delete this package. * * @param string $package_id Package id. * @return true|WP_Error */ protected function package_owner_check( string $package_id ) { if ( current_user_can( 'manage_options' ) || Job_Store::made_by( $package_id, get_current_user_id() ) ) { return true; } return new WP_Error( 'transtrix_forbidden', __( 'Only the person who made or uploaded this package, or an administrator, can use it.', 'transtrix' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Refuse a whole-site package to anyone below the full-site gate. * * A whole-site package carries the user table and site secrets. One whose * manifest cannot be read is treated the same way. * * @param string $path Stored package path. * @return true|WP_Error */ protected function full_site_package_check( string $path ) { if ( $this->can_access_full_site() ) { return true; } $manifest = Package_Reader::peek( $path ); return is_wp_error( $manifest ) || 'full-site' === ( $manifest['kind'] ?? '' ) ? $this->full_site_forbidden() : true; } /** * The error a non-admin gets for a whole-site job or package. * * @return WP_Error */ protected function full_site_forbidden(): WP_Error { return new WP_Error( 'transtrix_forbidden', __( 'Only an administrator can do this.', 'transtrix' ), array( 'status' => rest_authorization_required_code() ) ); } /** * An error the UI can switch on. * * @param string $code Machine-readable code. * @param string $message Human message. * @param int $status HTTP status. * @return WP_Error */ protected function error( string $code, string $message, int $status = 400 ): WP_Error { return new WP_Error( $code, $message, array( 'status' => $status ) ); } /** * Re-shape an engine WP_Error so it carries an HTTP status. * * @param WP_Error $error The engine's error. * @param int $status HTTP status to attach. * @return WP_Error */ protected function from_engine( WP_Error $error, int $status = 400 ): WP_Error { return new WP_Error( $error->get_error_code(), $error->get_error_message(), array( 'status' => $status ) ); } /** * The package id argument, shared by every /import//* route. * * @return array> */ protected function package_id_arg(): array { return array( 'package_id' => array( 'description' => __( 'Id of an uploaded package.', 'transtrix' ), 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_key', 'validate_callback' => static function ( $value ) { return 1 === preg_match( '/^[a-f0-9]{32}$/', (string) $value ); }, ), ); } }