plugin = $plugin; } /** * Hook route registration. */ public function register() { add_action( 'rest_api_init', array( $this, 'routes' ) ); } /** * Permission check shared by every admin route. * * @return bool */ public function can_manage() { return current_user_can( 'manage_options' ); } /** * Register routes. */ public function routes() { $id = '(?P[a-z0-9_\-]+)'; $post = array( '/settings' => 'save_setting', '/override' => 'save_override', '/override/undo' => 'undo_override', '/scan' => 'scan', '/policy/recheck' => 'policy_recheck', '/activate' => 'activate', '/reprompt' => 'reprompt', '/terms/accept' => 'accept_terms', "/issues/$id/add" => 'add_wording', "/issues/$id/handle" => 'handle', "/issues/$id/dismiss" => 'dismiss', "/pages/$id/preview" => 'page_preview', "/pages/$id/apply" => 'page_apply', "/pages/$id/publish" => 'page_publish', "/pages/$id/undo" => 'page_undo', ); foreach ( $post as $route => $cb ) { register_rest_route( self::NS, $route, array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, $cb ), 'permission_callback' => array( $this, 'can_manage' ), ) ); } register_rest_route( self::NS, '/state', array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_state' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); register_rest_route( self::NS, '/traffic', array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'traffic' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'days' => array( 'type' => 'integer', 'enum' => array( 7, 30, 90 ), 'default' => 30, ), ), ) ); } /** * Standard success response: message + fresh state. * * @param string $message Toast message. * @return \WP_REST_Response */ private function ok( $message ) { return rest_ensure_response( array( 'message' => $message, 'state' => State::build( $this->plugin ), ) ); } /** * GET /state. * * @return \WP_REST_Response */ public function get_state() { return rest_ensure_response( State::build( $this->plugin ) ); } /** * POST /settings {key, value}. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function save_setting( \WP_REST_Request $r ) { $key = sanitize_key( (string) $r->get_param( 'key' ) ); if ( in_array( $key, array( 'overrides', 'enforce_at', 'version' ), true ) ) { return new \WP_Error( 'goconsent_readonly', __( 'That setting can’t be changed here.', 'goconsent' ), array( 'status' => 400 ) ); } if ( 'banner_mode' === $key && 'us_optout' === $r->get_param( 'value' ) ) { $blockers = \GoConsent\Frontend\BannerMode::blockers( $this->plugin->settings ); if ( $blockers ) { return new \WP_Error( 'goconsent_banner_needed', /* translators: %s: list of tools, e.g. "Meta (Facebook) Pixel, Microsoft Clarity". */ sprintf( __( 'The banner has to stay on. Found: %s.', 'goconsent' ), implode( ', ', $blockers ) ), array( 'status' => 400 ) ); } } $res = $this->plugin->settings->set( $key, $r->get_param( 'value' ) ); if ( is_wp_error( $res ) ) { return $res; } $label = sanitize_text_field( (string) $r->get_param( 'label' ) ); $label = '' !== $label ? $label : __( 'Setting', 'goconsent' ); if ( is_bool( $res ) ) { /* translators: %s: setting label. */ $msg = $res ? sprintf( __( '%s turned on.', 'goconsent' ), $label ) : sprintf( __( '%s turned off.', 'goconsent' ), $label ); } else { /* translators: %s: setting label. */ $msg = sprintf( __( '%s saved.', 'goconsent' ), $label ); } return $this->ok( $msg ); } /** * POST /override {id, category}. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response */ public function save_override( \WP_REST_Request $r ) { $id = sanitize_text_field( (string) $r->get_param( 'id' ) ); $cat = sanitize_key( (string) $r->get_param( 'category' ) ); $label = sanitize_text_field( (string) $r->get_param( 'label' ) ); if ( '' === $id || ! in_array( $cat, array( 'necessary', 'analytics', 'marketing' ), true ) ) { return new \WP_Error( 'goconsent_override', __( 'Unknown category.', 'goconsent' ), array( 'status' => 400 ) ); } $this->plugin->settings->set_override( $id, $cat ); Approvals::record( $id, $cat ); $this->plugin->report->rebuild( false ); $names = array( 'necessary' => __( 'Necessary', 'goconsent' ), 'analytics' => __( 'Analytics', 'goconsent' ), 'marketing' => __( 'Marketing', 'goconsent' ), ); if ( $r->get_param( 'approve' ) ) { /* translators: 1: tool name, 2: category. */ return $this->ok( sprintf( __( '%1$s approved as %2$s.', 'goconsent' ), $label, $names[ $cat ] ) ); } /* translators: 1: tool name, 2: category. */ return $this->ok( sprintf( __( '%1$s set to %2$s.', 'goconsent' ), $label, $names[ $cat ] ) ); } /** * POST /override/undo {id}: back to the scanner's own suggestion, needing review again. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response */ public function undo_override( \WP_REST_Request $r ) { $id = sanitize_text_field( (string) $r->get_param( 'id' ) ); $label = sanitize_text_field( (string) $r->get_param( 'label' ) ); $all = (array) $this->plugin->settings->get( 'overrides' ); unset( $all[ $id ] ); $this->plugin->settings->set( 'overrides', $all ); Approvals::forget( $id ); $this->plugin->report->rebuild( false ); /* translators: %s: tool name. */ return $this->ok( sprintf( __( 'Approval for %s undone.', 'goconsent' ), $label ) ); } /** * POST /scan. * * @return \WP_REST_Response */ public function scan() { if ( function_exists( 'set_time_limit' ) ) { set_time_limit( 180 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- scans fetch several pages. } $res = $this->plugin->scanner->run(); $this->plugin->report->rebuild( false ); $n = count( $res['pages'] ); /* translators: %d: number of pages. */ return $this->ok( sprintf( _n( 'Scan complete: %d page checked.', 'Scan complete: %d pages checked.', $n, 'goconsent' ), $n ) ); } /** * POST /policy/recheck: re-read the privacy page and update the report (after an edit). * * @return \WP_REST_Response */ public function policy_recheck() { $before = count( $this->plugin->report->open() ); $this->plugin->scanner->refresh_policy(); $this->plugin->report->rebuild( false ); $fixed = max( 0, $before - count( $this->plugin->report->open() ) ); /* translators: %d: number of issues. */ return $this->ok( $fixed ? sprintf( _n( 'Privacy policy re-checked: %d issue fixed.', 'Privacy policy re-checked: %d issues fixed.', $fixed, 'goconsent' ), $fixed ) : '' ); } /** * POST /activate: end the 24-hour watch period now. * * @return \WP_REST_Response */ public function activate() { $this->plugin->settings->set( 'enforce_at', time() - 1 ); return $this->ok( __( 'Activated. Unfamiliar scripts now wait for visitor permission.', 'goconsent' ) ); } /** * POST /reprompt: ask every visitor again. * * @return \WP_REST_Response */ public function reprompt() { $this->plugin->settings->set( 'version', absint( $this->plugin->settings->get( 'version' ) ) + 1 ); return $this->ok( __( 'Done. Every visitor will see the banner again on their next visit.', 'goconsent' ) ); } /** * POST /terms/accept. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function accept_terms( \WP_REST_Request $r ) { if ( ! rest_sanitize_boolean( $r->get_param( 'agree' ) ) ) { return new \WP_Error( 'goconsent_agree', __( 'Check the box to agree to the terms.', 'goconsent' ), array( 'status' => 400 ) ); } $this->plugin->terms->accept( wp_get_current_user() ); update_option( 'goconsent_scan_pending', 1, false ); if ( ! wp_next_scheduled( Cron::SCAN_1X ) ) { wp_schedule_single_event( time() + 5, Cron::SCAN_1X ); } return $this->ok( __( 'Agreement saved. GoConsent is active.', 'goconsent' ) ); } /** * POST /issues/{id}/add: append suggested wording to the privacy policy. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function add_wording( \WP_REST_Request $r ) { $id = (string) $r->get_param( 'id' ); $wording = $this->plugin->report->wording->get( $id ); if ( ! $wording ) { return new \WP_Error( 'goconsent_no_wording', __( 'There’s no suggested wording for this item.', 'goconsent' ), array( 'status' => 400 ) ); } $res = PolicyEditor::append( $this->plugin->settings->privacy_page_id(), $wording['heading'], $wording['body'] ); if ( is_wp_error( $res ) ) { return $res; } $this->plugin->report->resolve( $id, 'added' ); $this->plugin->report->rebuild( false ); return $this->ok( __( 'Added to your privacy policy. A revision was saved so you can undo it.', 'goconsent' ) ); } /** * POST /issues/{id}/handle {reason}. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function handle( \WP_REST_Request $r ) { $reason = trim( sanitize_text_field( (string) $r->get_param( 'reason' ) ) ); if ( '' === $reason ) { return new \WP_Error( 'goconsent_reason', __( 'Add a short reason so there’s a record of how this was handled.', 'goconsent' ), array( 'status' => 400 ) ); } $res = $this->plugin->report->resolve( (string) $r->get_param( 'id' ), 'handled', $reason ); return is_wp_error( $res ) ? $res : $this->ok( __( 'Marked as handled.', 'goconsent' ) ); } /** * POST /issues/{id}/dismiss (info only). * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function dismiss( \WP_REST_Request $r ) { $res = $this->plugin->report->resolve( (string) $r->get_param( 'id' ), 'dismissed' ); return is_wp_error( $res ) ? $res : $this->ok( __( 'Dismissed. It comes back only if something changes.', 'goconsent' ) ); } /** * GET /traffic?days=30. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response */ public function traffic( \WP_REST_Request $r ) { return rest_ensure_response( $this->plugin->traffic->report( (int) $r->get_param( 'days' ) ) ); } /** * Policy page id from the route, if it's one GoConsent builds. * * @param \WP_REST_Request $r Request. * @return string|\WP_Error */ private function page_id( \WP_REST_Request $r ) { $id = sanitize_key( (string) $r->get_param( 'id' ) ); if ( ! isset( \GoConsent\Policies\Pages::catalog()[ $id ] ) ) { return new \WP_Error( 'goconsent_page', __( 'Unknown page.', 'goconsent' ), array( 'status' => 404 ) ); } if ( ! current_user_can( 'edit_pages' ) ) { return new \WP_Error( 'goconsent_forbidden', __( 'You can’t edit pages.', 'goconsent' ), array( 'status' => 403 ) ); } return $id; } /** * Run a page action and answer with a message and fresh state. * * @param \WP_REST_Request $r Request. * @param string $method Manager method. * @return \WP_REST_Response|\WP_Error */ private function page_action( \WP_REST_Request $r, $method ) { $id = $this->page_id( $r ); if ( is_wp_error( $id ) ) { return $id; } $res = call_user_func( array( 'GoConsent\\Policies\\Manager', $method ), $id ); if ( is_wp_error( $res ) ) { return $res; } $out = $this->ok( $res['message'] )->get_data(); $out['page'] = $res['page']; return rest_ensure_response( $out ); } /** * POST /pages/{id}/preview: suggested text and what an update would change. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function page_preview( \WP_REST_Request $r ) { $id = $this->page_id( $r ); if ( is_wp_error( $id ) ) { return $id; } $res = \GoConsent\Policies\Manager::preview( $id ); return is_wp_error( $res ) ? $res : rest_ensure_response( $res ); } /** * POST /pages/{id}/apply: create (draft) or update the page. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function page_apply( \WP_REST_Request $r ) { return $this->page_action( $r, 'apply' ); } /** * POST /pages/{id}/publish. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function page_publish( \WP_REST_Request $r ) { return $this->page_action( $r, 'publish' ); } /** * POST /pages/{id}/undo. * * @param \WP_REST_Request $r Request. * @return \WP_REST_Response|\WP_Error */ public function page_undo( \WP_REST_Request $r ) { return $this->page_action( $r, 'undo' ); } }