This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,110 @@
<?php
namespace Vrts\Rest_Api;
use WP_Error;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Controller;
use Vrts\Models\Alert;
use Vrts\Features\Service;
class Rest_Alerts_Controller extends WP_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'vrts/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'alerts';
/**
* Constructor.
*/
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
/**
* Registers the routes for alerts.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/false-positive', [
'methods' => [ WP_REST_Server::CREATABLE, WP_REST_Server::DELETABLE ],
'callback' => [ $this, 'false_positive_callback' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
] );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/read-status', [
'methods' => [ WP_REST_Server::CREATABLE, WP_REST_Server::DELETABLE ],
'callback' => [ $this, 'read_status_callback' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
] );
}
/**
* Checks if a given request has access to get items.
*
* @param WP_REST_Request $request Request.
*/
public function get_items_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
/**
* Deletes a false positive.
*
* @param WP_REST_Request $request Current request.
*/
public function false_positive_callback( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
$alert = Alert::get_item( $id );
if ( ! $alert ) {
return new WP_Error( 'error', esc_html__( 'Alert not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
$service = new Service();
$should_flag_false_positive = $request->get_method() === WP_REST_Server::CREATABLE;
Alert::set_false_positive( $id, $should_flag_false_positive );
if ( $should_flag_false_positive ) {
$service->mark_alert_as_false_positive( $id );
} else {
$service->unmark_alert_as_false_positive( $id );
}
return new WP_REST_Response( true, 200 );
}
/**
* Read status.
*
* @param WP_REST_Request $request Current request.
*/
public function read_status_callback( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
$alert = Alert::get_item( $id );
if ( ! $alert ) {
return new WP_Error( 'error', esc_html__( 'Alert not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
$should_mark_as_read = $request->get_method() === WP_REST_Server::CREATABLE ? 1 : 0;
Alert::set_alert_state( $id, $should_mark_as_read );
return new WP_REST_Response( true, 200 );
}
}
@@ -0,0 +1,260 @@
<?php
namespace Vrts\Rest_Api;
use WP_Error;
use WP_REST_Request;
use Vrts\Features\Subscription;
use Vrts\Models\Test_Run;
use Vrts\Services\Test_Run_Service;
use Vrts\Services\Test_Service;
class Rest_Service_Controller {
/**
* Namespace.
*
* @var string
*/
private $namespace;
/**
* Resource name.
*
* @var string
*/
private $resource_name;
/**
* Constructor.
*/
public function __construct() {
$this->namespace = 'vrts/v1';
$this->resource_name = 'service';
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
add_action( 'wp_ajax_nopriv_vrts_service', [ $this, 'ajax_action' ] );
add_action( 'wp_ajax_priv_vrts_service', [ $this, 'ajax_action' ] );
}
/**
* Register routes.
*/
public function register_routes() {
register_rest_route($this->namespace, $this->resource_name, [
'methods' => [ 'POST' ],
'callback' => [ $this, 'service_callback' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
]);
}
/**
* Actions for admin-ajax.php
*/
public function ajax_action() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- It's ok.
$data = json_decode( wp_unslash( $_REQUEST['data'] ?? '' ), true );
$rest_response = $this->perform_action( $data ?? [] );
// If rest response is WP error, get the status code.
if ( is_wp_error( $rest_response ) ) {
$error_data = $rest_response->get_error_data();
status_header( $error_data['status'] );
wp_send_json( $rest_response->get_error_message() );
} else {
status_header( $rest_response->get_status() );
wp_send_json( $rest_response->get_data() );
}
}
/**
* Gets some data.
*
* @param WP_REST_Request $request Current request.
*/
public function service_callback( WP_REST_Request $request ) {
$data = $request->get_params();
return $this->perform_action( $data );
}
/**
* Perform ajax actions.
*
* @param array $data Current ajax data.
*/
public function perform_action( $data ) {
if ( ! array_key_exists( 'action', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Action parameter is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
switch ( $data['action'] ) {
case 'test_updated':
$response = $this->test_updated_request( $data );
break;
case 'run_updated':
$response = $this->run_updated_request( $data );
break;
case 'run_deleted':
$response = $this->run_deleted_request( $data );
break;
case 'subscription_changed':
$response = $this->subscription_changed_request();
break;
default:
$response = $this->unknown_action_request();
break;
}//end switch
return $response;
}
/**
* Test updated request
*
* @param array $data Rest api response body.
*/
private function test_updated_request( $data ) {
if ( ! array_key_exists( 'project_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Project id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( get_option( 'vrts_project_id' ) !== $data['project_id'] ) {
return new WP_Error( 'error', esc_html__( 'Project id does not match.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( ! array_key_exists( 'test_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Test id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
if ( ! self::verify_signature( $data ) ) {
return new WP_Error( 'error', esc_html__( 'Signature is not valid.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
$test_service = new Test_Service();
if ( $test_service->update_test_from_api_data( $data ) ) {
Subscription::update_available_tests( $data['remaining_credits'], $data['total_credits'], $data['has_subscription'], $data['tier_id'] );
return rest_ensure_response([
'message' => 'Action test_updated successful.',
]);
}//end if
return new WP_Error( 'error', esc_html__( 'Test not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
/**
* Test Run updated request
*
* @param array $data Rest api response body.
*/
private function run_updated_request( $data ) {
if ( ! array_key_exists( 'project_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Project id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( get_option( 'vrts_project_id' ) !== $data['project_id'] ) {
return new WP_Error( 'error', esc_html__( 'Project id does not match.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( ! array_key_exists( 'run_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Run id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
if ( ! self::verify_signature( $data ) ) {
return new WP_Error( 'error', esc_html__( 'Signature is not valid.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
$test_run_service = new Test_Run_Service();
if ( $test_run_service->update_run_from_api_data( $data ) ) {
Subscription::update_available_tests( $data['remaining_credits'], $data['total_credits'], $data['has_subscription'], $data['tier_id'] );
return rest_ensure_response([
'message' => 'Action run_updated successful.',
]);
}//end if
return new WP_Error( 'error', esc_html__( 'Test not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
/**
* Test Run updated request
*
* @param array $data Rest api response body.
*/
private function run_deleted_request( $data ) {
if ( ! array_key_exists( 'project_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Project id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( get_option( 'vrts_project_id' ) !== $data['project_id'] ) {
return new WP_Error( 'error', esc_html__( 'Project id does not match.', 'visual-regression-tests' ), [ 'status' => 403 ] );
} elseif ( ! array_key_exists( 'run_id', $data ) ) {
return new WP_Error( 'error', esc_html__( 'Run id is missing.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
if ( ! self::verify_signature( $data ) ) {
return new WP_Error( 'error', esc_html__( 'Signature is not valid.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
$test_run_service = new Test_Run_Service();
if ( Test_Run::delete_by_service_test_run_id( $data['run_id'] ) ) {
return rest_ensure_response([
'message' => 'Action run_deleted successful.',
]);
}//end if
return new WP_Error( 'error', esc_html__( 'Test not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
/**
* Verify signature
*
* @param array $data Rest api response body.
*
* @return bool
*/
private function verify_signature( $data ) {
$signature = $data['signature'];
unset( $data['signature'] );
$secret = get_option( 'vrts_project_secret' ) || 'verysecret';
return hash_equals( $signature, hash_hmac( 'sha256', wp_json_encode( $data ), $secret ) );
}
/**
* Subscription changed request
*/
private function subscription_changed_request() {
// When notified about subscription change from service, update the tests with the new status.
Subscription::get_latest_status();
return rest_ensure_response([
'message' => esc_html__( 'Subscription changed action was successful.', 'visual-regression-tests' ),
]);
}
/**
* Unknown action request
*/
private function unknown_action_request() {
return new WP_Error( 'error', esc_html__( 'Unknown action.', 'visual-regression-tests' ), [ 'status' => 403 ] );
}
/**
* Check permissions for the requets.
*/
public function get_items_permissions_check() {
return true;
}
/**
* Sets up the proper HTTP status code for authorization.
*/
public function authorization_status_code() {
$status = 401;
if ( is_user_logged_in() ) {
$status = 403;
}
return $status;
}
}
@@ -0,0 +1,76 @@
<?php
namespace Vrts\Rest_Api;
use WP_Error;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Controller;
use Vrts\Models\Alert;
use Vrts\Models\Test_Run;
class Rest_Test_Runs_Controller extends WP_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'vrts/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'test-runs';
/**
* Constructor.
*/
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
/**
* Registers the routes for alerts.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/read-status', [
'methods' => [ WP_REST_Server::CREATABLE, WP_REST_Server::DELETABLE ],
'callback' => [ $this, 'read_status_callback' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
] );
}
/**
* Checks if a given request has access to get items.
*
* @param WP_REST_Request $request Request.
*/
public function get_items_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
/**
* Read status.
*
* @param WP_REST_Request $request Current request.
*/
public function read_status_callback( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
$test_run = Test_Run::get_item( $id );
if ( ! $test_run ) {
return new WP_Error( 'error', esc_html__( 'Run not found.', 'visual-regression-tests' ), [ 'status' => 404 ] );
}
$should_mark_as_read = $request->get_method() === WP_REST_Server::CREATABLE ? 1 : 0;
Alert::set_read_status_by_test_run( $id, $should_mark_as_read );
return new WP_REST_Response( true, 200 );
}
}
@@ -0,0 +1,185 @@
<?php
namespace Vrts\Rest_Api;
use Vrts\Features\Subscription;
use WP_REST_Request;
use WP_Error;
use WP_REST_Server;
use Vrts\Models\Test;
use Vrts\Services\Test_Service;
class Rest_Tests_Controller {
/**
* Namespace.
*
* @var string
*/
private $namespace;
/**
* Resource name.
*
* @var string
*/
private $resource_name;
/**
* Constructor.
*/
public function __construct() {
$this->namespace = 'vrts/v1';
$this->resource_name = 'tests';
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
/**
* Register routes.
*/
public function register_routes() {
register_rest_route($this->namespace, $this->resource_name, [
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'tests_remaining_total_callback' ],
'permission_callback' => '__return_true',
]);
register_rest_route($this->namespace, $this->resource_name . '/post/(?P<post_id>\d+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'tests_callback' ],
'permission_callback' => '__return_true',
]);
register_rest_route($this->namespace, $this->resource_name . '/post/(?P<post_id>\d+)', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'create_test_callback' ],
'permission_callback' => [ $this, 'user_can_create' ],
]);
register_rest_route($this->namespace, $this->resource_name . '/post/(?P<post_id>\d+)', [
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'delete_test_callback' ],
'permission_callback' => [ $this, 'user_can_create' ],
]);
register_rest_route($this->namespace, $this->resource_name . '/post/(?P<post_id>\d+)', [
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'update_test_callback' ],
'permission_callback' => [ $this, 'user_can_create' ],
]);
}
/**
* Gets some tests data.
*
* @param WP_REST_Request $request Current request.
*/
public function tests_callback( WP_REST_Request $request ) {
$data = $request->get_params();
$post_id = $data['post_id'] ?? 0;
if ( 0 !== $post_id ) {
$test = Test::get_item_by_post_id( $post_id );
if ( ! empty( $test ) ) {
$test = Test::cast_values( $test );
return rest_ensure_response( $test, 200 );
}
}
$error = new WP_Error(
'rest_not_found',
esc_html__( 'The test does not exist.', 'visual-regression-tests' ),
[ 'status' => 404 ] );
return rest_ensure_response( $error );
}
/**
* Get remaining and total tests.
*/
public function tests_remaining_total_callback() {
return rest_ensure_response([
'remaining_tests' => (int) Subscription::get_remaining_tests(),
'total_tests' => (int) Subscription::get_total_tests(),
], 200);
}
/**
* Creates a test.
*
* @param WP_REST_Request $request Current request.
*/
public function create_test_callback( WP_REST_Request $request ) {
$data = $request->get_params();
$post_id = $data['post_id'] ?? 0;
if ( 0 !== $post_id ) {
$service = new Test_Service();
$test = $service->create_test( $post_id );
return rest_ensure_response( Test::cast_values( $test ), 201 );
}
$error = new WP_Error(
'rest_create_test_failed',
esc_html__( 'The test could not be created.', 'visual-regression-tests' ),
[ 'status' => 400 ] );
return rest_ensure_response( $error );
}
/**
* Deletes a test.
*
* @param WP_REST_Request $request Current request.
*/
public function delete_test_callback( WP_REST_Request $request ) {
$data = $request->get_params();
$post_id = $data['post_id'] ?? 0;
if ( 0 !== $post_id ) {
$test = Test::get_item_by_post_id( $post_id );
$service = new Test_Service();
$service->delete_test( (int) $test->id );
return rest_ensure_response( [], 200 );
}
$error = new WP_Error(
'rest_delete_test_failed',
esc_html__( 'The test could not be deleted.', 'visual-regression-tests' ),
[ 'status' => 400 ] );
return rest_ensure_response( $error );
}
/**
* Updates a test.
*
* @param WP_REST_Request $request Current request.
*/
public function update_test_callback( WP_REST_Request $request ) {
$data = $request->get_params();
$post_id = $data['post_id'] ?? 0;
$test_id = $data['test_id'] ?? 0;
$hide_css_selectors = $data['hide_css_selectors'] ?? [];
if ( 0 !== $post_id && 0 !== $test_id && ! empty( $hide_css_selectors ) ) {
$service = new Test_Service();
$updated = $service->update_css_hide_selectors( $test_id, $hide_css_selectors );
if ( $updated && ! is_wp_error( $updated ) ) {
$service->resume_test( $post_id );
$test = Test::get_item_by_post_id( $post_id );
if ( ! empty( $test ) ) {
return rest_ensure_response( Test::cast_values( $test ), 200 );
}
}
}
$error = new WP_Error(
'rest_update_test_failed',
esc_html__( 'The test could not be updated.', 'visual-regression-tests' ),
[ 'status' => 400 ] );
return rest_ensure_response( $error );
}
/**
* Checks if a given request has access to create items.
*
* @return bool True if the request has access to create items, false otherwise.
*/
public function user_can_create() {
return current_user_can( 'manage_options' );
}
}