settings = $settings; } /** * 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( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get' ), 'permission_callback' => array( $this, 'can_manage' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'save' ), 'permission_callback' => array( $this, 'can_manage' ), ), ) ); register_rest_route( self::NAMESPACE, self::ROUTE . '/create', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create' ), 'permission_callback' => array( $this, 'can_create' ), ) ); } /** * Whether the current user may manage the plugin. */ public function can_manage(): bool { return current_user_can( Capabilities::MANAGE ); } /** * Whether the current user may manage the plugin and create pages. */ public function can_create(): bool { return $this->can_manage() && current_user_can( 'publish_pages' ); } /** * The policy page, the pages to choose from and the settings. */ public function get(): WP_REST_Response { return new WP_REST_Response( $this->payload() ); } /** * Save the policy page. * * @param WP_REST_Request $request Request whose JSON body holds `policy_page_id`. * @return WP_REST_Response|WP_Error */ public function save( WP_REST_Request $request ) { $body = $request->get_json_params(); if ( ! is_array( $body ) ) { return new WP_Error( 'consentfold_invalid_body', __( 'Send the policy settings as a JSON object.', 'consentfold' ), array( 'status' => 400 ) ); } $errors = array(); foreach ( array_keys( array_diff_key( $body, array( 'policy_page_id' => true ) ) ) as $field ) { $errors[ (string) $field ] = __( 'This setting cannot be changed here.', 'consentfold' ); } if ( array_key_exists( 'policy_page_id', $body ) && $this->locked() ) { return self::locked_error(); } $id = $body['policy_page_id'] ?? null; if ( is_int( $id ) && $id > 0 && null === self::page( $id ) ) { $errors['policy_page_id'] = __( 'Choose an existing page.', 'consentfold' ); } if ( array() !== $errors ) { return self::invalid_settings( $errors ); } if ( array_key_exists( 'policy_page_id', $body ) ) { $error = $this->store( $body['policy_page_id'] ); if ( null !== $error ) { return $error; } } return new WP_REST_Response( $this->payload() ); } /** * Create a draft page that holds the cookie policy block and make it the policy page. A policy page that exists * already is left alone, so a second click does not create another page. * * @return WP_REST_Response|WP_Error */ public function create() { if ( $this->locked() ) { return self::locked_error(); } if ( null !== self::page( (int) $this->settings->get()['policy_page_id'] ) ) { return new WP_Error( 'consentfold_policy_page_exists', __( 'A policy page is set already. Clear it first to create another.', 'consentfold' ), array( 'status' => 409 ) ); } $id = wp_insert_post( array( 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => __( 'Cookie policy', 'consentfold' ), 'post_content' => '', ), true ); if ( is_wp_error( $id ) ) { return new WP_Error( 'consentfold_page_not_created', __( 'The page could not be created.', 'consentfold' ), array( 'status' => 500 ) ); } $error = $this->store( $id ); if ( null !== $error ) { wp_delete_post( $id, true ); return $error; } return new WP_REST_Response( $this->payload() ); } /** * Save the policy page id through the repository. * * @param mixed $id Page id; the schema rejects anything but a non-negative integer. * @return WP_Error|null Null when it was saved. */ private function store( $id ): ?WP_Error { $document = array_merge( $this->settings->get_site(), array( 'policy_page_id' => $id ) ); unset( $document['schema_version'] ); // The repository carries it over. try { $this->settings->set( $document ); } catch ( InvalidSettingsException $e ) { return self::invalid_settings( $e->errors() ); } return null; } /** * What the tab shows: the policy page id, the page it names (null when it is gone), the pages to choose from and the locks. * * @return array{policy_page_id: int, page: array|null, pages: list, locked: list} */ private function payload(): array { $id = (int) $this->settings->get()['policy_page_id']; $page = self::page( $id ); $list = array(); $pages = get_pages( array( 'post_status' => 'publish,draft,pending,private,future', 'sort_column' => 'post_title', 'number' => self::MAX_PAGES, ) ); foreach ( is_array( $pages ) ? $pages : array() as $candidate ) { $list[ $candidate->ID ] = self::summary( $candidate ); } if ( null !== $page && ! isset( $list[ $id ] ) ) { $list[ $id ] = self::summary( $page ); } return array( 'policy_page_id' => $id, 'page' => null === $page ? null : self::summary( $page ) + array( 'edit_url' => (string) get_edit_post_link( $page->ID, 'raw' ), 'view_url' => (string) get_permalink( $page ), ), 'pages' => array_values( $list ), 'locked' => $this->locked() ? self::LOCKABLE : array(), ); } /** * The page with this id, if there is one that is not in the trash. * * @param int $id Page id. */ private static function page( int $id ): ?WP_Post { $post = $id > 0 ? get_post( $id ) : null; return $post instanceof WP_Post && 'page' === $post->post_type && 'trash' !== $post->post_status ? $post : null; } /** * What the tab lists of a page. * * @param WP_Post $post Page. * @return array{id: int, title: string, status: string} */ private static function summary( WP_Post $post ): array { return array( 'id' => $post->ID, 'title' => get_the_title( $post ), 'status' => $post->post_status, ); } /** * Whether the network locked the policy page. */ private function locked(): bool { return in_array( 'policy_page_id', $this->settings->locked_keys(), true ); } /** * Error for a write to a field the network locked. */ private static function locked_error(): WP_Error { return new WP_Error( 'consentfold_locked', __( 'The network administrator set this and it cannot be changed here.', 'consentfold' ), array( 'status' => 409 ) ); } /** * Error that carries the per-field messages. * * @param array $errors Messages keyed by dotted settings path. */ private static function invalid_settings( array $errors ): WP_Error { return new WP_Error( 'consentfold_invalid_settings', __( 'The policy settings are not valid.', 'consentfold' ), array( 'status' => 400, 'errors' => $errors, ) ); } }