<?php
/**
 * Defines Activity REST API
 *
 * @link https://personalizewp.com/
 * @since 3.2.0
 *
 * @package PersonalizeWP
 * @subpackage PersonalizeWP/REST
 */

namespace PersonalizeWP\REST;

use PersonalizeWP\Traits\SingletonTrait;
use PersonalizeWP\Model\Activity;
use PersonalizeWP\Model\Contact;

defined( 'ABSPATH' ) || exit; // Exit if accessed directly.

/**
 * Activity Controller class
 */
class Activity_Controller {

	use SingletonTrait;

	/**
	 * Endpoint namespace.
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	protected $namespace;

	/**
	 * Holds Instance of plugin object
	 *
	 * @var PersonalizeWP
	 */
	private $plugin;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @param string $ns Namespace
	 */
	public function __construct( $ns ) {
		$this->namespace = $ns;
		$this->plugin    = \personalizewp();
	}

	/**
	 * Registers the routes for the activity controller.
	 *
	 * @since 3.2.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			"{$this->namespace}v1",
			'/activity',
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_activity' ],
				// Register our expected args to validate and sanitise.
				'args'                => $this->get_activity_endpoint_args(),
				'permission_callback' => '__return_true', // nosemgrep: audit.php.wp.security.rest-route.permission-callback.return-true -- Reason: Public API
			]
		);
	}

	/**
	 * Retrieves an array of endpoint arguments for the Activity endpoint.
	 *
	 * @since 3.2.0 Merged into PWP
	 * @since 1.0.0
	 *
	 * @return array
	 */
	protected function get_activity_endpoint_args(): array {

		$args = array(
			'uid'      => array(
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => [ 'PersonalizeWP\API', 'validate_non_empty_string' ],
				'sanitize_callback' => [ 'PersonalizeWP\API', 'sanitize_arg' ],
			),

			'type'     => array(
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => [ $this, 'validate_known_event_types' ],
				'sanitize_callback' => [ 'PersonalizeWP\API', 'sanitize_arg' ],
			),

			'url'      => array(
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => [ 'PersonalizeWP\API', 'validate_non_empty_string' ],
				'sanitize_callback' => [ 'PersonalizeWP\API', 'sanitize_arg' ],
			),

			'object'   => array(
				'type'              => 'integer',
				'required'          => false,
				'sanitize_callback' => [ 'PersonalizeWP\API', 'sanitize_arg' ],
			),

			'referrer' => array(
				'type'              => 'string',
				'required'          => false,
				'validate_callback' => [ 'PersonalizeWP\API', 'validate_non_empty_string' ],
				'sanitize_callback' => [ 'PersonalizeWP\API', 'sanitize_arg' ],
			),

			// Optional key/value pairs of extra data to record against the activity entry.
			'data'     => array(
				'type'              => 'array',
				'required'          => false,
				'validate_callback' => [ 'PersonalizeWP\API', 'validate_non_empty_array' ],
				'sanitize_callback' => [ $this, 'sanitize_custom_event_data' ],
			),
		);

		return $args;
	}

	/**
	 * Validate the event type is a string and a known type.
	 *
	 * @param  string           $type    Event type.
	 * @param  \WP_REST_Request $request The current request object.
	 * @param  string           $param   Key of the parameter.
	 * @return boolean
	 */
	public function validate_known_event_types( $type, $request, $param ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		// Check against all registered events.
		$event_types = personalizewp()->get_registered_event_types();
		if ( isset( $event_types[ $type ] ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Sanitise the data arg.
	 *
	 * @param  array            $data    Data for the event.
	 * @param  \WP_REST_Request $request The current request object.
	 * @param  string           $param   Key of the parameter.
	 * @return array
	 */
	public function sanitize_custom_event_data( $data, $request, $param ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$data = map_deep( $data, 'sanitize_text_field' );

		return $data;
	}

	/**
	 * Handles creation of activity request
	 *
	 * @since 3.2.0 Merged into PWP
	 * @since 1.0.0
	 *
	 * @param \WP_REST_Request $request api request
	 *
	 * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response
	 */
	public function create_activity( \WP_REST_Request $request ) {

		$params = $request->get_params();

		$activity_args = [
			'url'           => $params['url'],
			'activity_type' => $params['type'],
		];
		if ( ! empty( $params['object'] ) ) {
			$activity_args['object_id'] = $params['object'];
			if ( ! empty( $params['type'] ) && 'post' === $params['type'] ) {
				$activity_args['object_type'] = get_post_type( $params['object'] );
			}
		}
		if ( ! empty( $params['referrer'] ) ) {
			$activity_args['referrer'] = $params['referrer'];
		}

		// Extended data for custom event types.
		if ( ! empty( $params['data'] ) ) {
			$activity_args['data'] = $params['data'];
		}

		$known_contact    = false;
		$activity_success = Activity::record( $params['uid'], $activity_args );
		if ( $activity_success ) {
			// Check if the user/uid is known, this happens even if in "do not track anon users" mode.
			$contact       = Contact::find_by_uid( $params['uid'] );
			$known_contact = $contact && $contact->is_known;
		}

		$response = array(
			'success' => $activity_success,
			'known'   => $known_contact,
		);

		return rest_ensure_response( $response );
	}
}
