<?php
/**
 * REST API masking for restricted content.
 *
 * @package Polanger_Dashboard_Tools
 */

if ( ! defined( 'ABSPATH' ) ) {
  exit;
}

class PDT_Content_Visibility_REST {

  /**
   * Register REST hooks.
   *
   * @return void
   */
  public function register_hooks() {
    add_action( 'rest_api_init', array( $this, 'register_rest_query_filters' ) );
    add_filter( 'rest_pre_dispatch', array( $this, 'guard_single_rest_requests' ), 10, 3 );
    add_filter( 'rest_post_search_query', array( $this, 'filter_rest_post_search_query' ), 10, 2 );
  }

  /**
   * Register collection query filters for supported post types.
   *
   * @return void
   */
  public function register_rest_query_filters() {
    foreach ( PDT_Content_Visibility::get_supported_post_types() as $post_type ) {
      add_filter(
        'rest_' . $post_type . '_query',
        function( $args, $request ) use ( $post_type ) {
          return $this->filter_rest_collection_query( $args, $request, $post_type );
        },
        10,
        2
      );
    }
  }

  /**
   * Exclude restricted posts from REST collections.
   *
   * @param array           $args     Controller query args.
   * @param WP_REST_Request $request  REST request.
   * @param string          $post_type Post type.
   * @return array
   */
  public function filter_rest_collection_query( $args, $request, $post_type ) {
    if ( ! PDT_Content_Visibility::should_hide_from_rest() ) {
      return $args;
    }

    if ( 'edit' === $request->get_param( 'context' ) ) {
      $object = get_post_type_object( $post_type );

      if ( $object && current_user_can( $object->cap->edit_posts ) ) {
        return $args;
      }
    }

    return PDT_Content_Visibility::append_visibility_query_args( $args );
  }

  /**
   * Exclude restricted posts from the shared REST search endpoint.
   *
   * @param array           $args    Search query args.
   * @param WP_REST_Request $request REST request.
   * @return array
   */
  public function filter_rest_post_search_query( $args, $request ) {
    if ( ! PDT_Content_Visibility::should_hide_from_rest() ) {
      return $args;
    }

    unset( $request );
    return PDT_Content_Visibility::append_visibility_query_args( $args );
  }

  /**
   * Block single item REST reads when content is restricted.
   *
   * @param mixed           $result  Current result.
   * @param WP_REST_Server  $server  REST server.
   * @param WP_REST_Request $request REST request.
   * @return mixed
   */
  public function guard_single_rest_requests( $result, $server, $request ) {
    unset( $server );

    if ( ! PDT_Content_Visibility::should_hide_from_rest() ) {
      return $result;
    }

    if ( ! in_array( $request->get_method(), array( 'GET', 'HEAD' ), true ) ) {
      return $result;
    }

    $post_type = $this->resolve_rest_post_type( $request );
    $post_id   = absint( $request->get_param( 'id' ) );

    if ( '' === $post_type || ! $post_id || ! PDT_Content_Visibility::is_supported_post( $post_id ) ) {
      return $result;
    }

    if ( 'edit' === $request->get_param( 'context' ) && current_user_can( 'edit_post', $post_id ) ) {
      return $result;
    }

    if ( PDT_Content_Visibility::can_user_view_post( $post_id ) ) {
      return $result;
    }

    return new WP_Error(
      'pdt_content_visibility_rest_forbidden',
      __( 'Sorry, you are not allowed to view this content.', 'polanger-admin-menu-manager' ),
      array( 'status' => 404 )
    );
  }

  /**
   * Resolve the requested post type from a REST route.
   *
   * @param WP_REST_Request $request REST request.
   * @return string
   */
  private function resolve_rest_post_type( $request ) {
    $route = trim( $request->get_route(), '/' );

    foreach ( PDT_Content_Visibility::get_supported_post_types() as $post_type ) {
      $object    = get_post_type_object( $post_type );
      $rest_base = ( $object && ! empty( $object->rest_base ) ) ? $object->rest_base : $post_type;

      if ( preg_match( '#^wp/v2/' . preg_quote( $rest_base, '#' ) . '/\d+$#', $route ) ) {
        return $post_type;
      }
    }

    return '';
  }
}
