<?php
/**
 * Ninja Forms Integration
 *
 * @link https://personalizewp.com/
 * @since 3.2.0 Merged with PWP
 * @since 2.1.0
 *
 * @package PersonalizeWP
 * @subpackage PersonalizeWP/Integrations
 */

namespace PersonalizeWP\Integrations\NinjaForms;

use PersonalizeWP\Model\Contact;
use PersonalizeWP\Model\Activity;

/**
 * PersonalizeWP - NinjaForms class
 */
class NinjaForms extends \NF_Abstracts_Action {

	// phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore, Generic.Commenting.DocComment.MissingShort
	/**
	 * @var string
	 */
	protected $_name = 'personalizewp';

	/**
	 * @var string
	 */
	protected $_timing = 'late';

	/**
	 * Store the form IDs we want to modify
	 *
	 * @var array
	 */
	protected $form_ids = [];

	/**
	 * Track adding of footer JS
	 *
	 * @var bool
	 */
	protected $script_added = false;

	/**
	 * This instance
	 *
	 * @var NinjaForms
	 */
	protected static $_instance = null;
	// phpcs:enable

	/**
	 * Return this.
	 *
	 * @since 2.1.0
	 *
	 * @return NinjaForms
	 */
	public static function get_instance() {
		if ( null === self::$_instance ) {
			self::$_instance = new self();
		}

		return self::$_instance;
	}

	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct();

		add_action( 'init', array( $this, 'init' ), 20 );
		add_filter( 'ninja_forms_loaded', array( $this, 'setup' ) );
	}

	/**
	 * Init
	 *
	 * @since 2.9.0
	 *
	 * @return void
	 */
	public function init() {

		$this->_nicename = esc_html__( 'PersonalizeWP Profiles', 'personalizewp' );
		$this->_settings = array_merge( $this->_settings, $this->get_pwp_fields() );
	}

	/**
	 * Setup the NF Addon actions/filters
	 *
	 * @since 2.1.0
	 *
	 * @return void
	 */
	public function setup() {

		// Hook into pre-form display to check if JS should be added.
		add_action( 'ninja_forms_before_form_display', array( $this, 'check_forms' ) );

		// Process all NF forms irregardless of assigned action to track submission
		add_action( 'ninja_forms_after_submission', array( $this, 'process_all_submissions' ) );
	}

	/**
	 * Get the PersonalizeWP field mappings
	 *
	 * @since 2.1.0
	 *
	 * @return array
	 */
	private function get_pwp_fields() {
		$fields = array(
			array(
				'name'           => 'pwp_first_name',
				'label'          => esc_html__( 'First Name', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_last_name',
				'label'          => esc_html__( 'Last Name', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_email_address',
				'label'          => esc_html__( 'Email Address', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_marketing_consent',
				'label'          => esc_html__( 'Marketing Consent', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_privacy_policy',
				'label'          => esc_html__( 'Privacy Policy', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_wp_user_id',
				'label'          => esc_html__( 'WordPress User ID', 'personalizewp' ),
				'value'          => '{wp:user_id}',
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
			array(
				'name'           => 'pwp_wp_user_role',
				'label'          => esc_html__( 'WordPress User Role', 'personalizewp' ),
				'type'           => 'textbox',
				'group'          => 'primary',
				'width'          => 'one-half',
				'use_merge_tags' => true,
			),
		);

		// Use empty contact to get extended fields list.
		$contact = new Contact( [] );
		foreach ( $contact->extended_fields as $name => $label ) {
			$fields[] = array(
				'name'           => "pwp_{$name}",
				'type'           => 'textbox',
				'group'          => 'primary',
				'label'          => esc_html( $label ),
				'width'          => 'one-half',
				'use_merge_tags' => true,
			);
		}

		return $fields;
	}

	/**
	 * Process the specific form submission action, mapping submitted form fields to PWP Visitor profiles
	 *
	 * @param array $action_settings Settings for the action
	 * @param int   $form_id         Submitted form ID
	 * @param array $form_data       Submitted form data
	 *
	 * @return array
	 */
	public function process( $action_settings, $form_id, $form_data ) {

		// Double check action
		if ( empty( $action_settings['type'] ) || $this->_name !== $action_settings['type'] ) {
			return $form_data;
		}

		$uid = sanitize_text_field( $form_data['extra']['pwp_tracked_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: already checked during NinjaForms processing

		if ( empty( $uid ) || 'pwp_hidden_value' === $uid ) {
			return $form_data;
		}

		$user_data = [];
		// Only update the Contact if fields have been mapped.
		$base_fields = array(
			'pwp_first_name'    => '',
			'pwp_last_name'     => '',
			'pwp_email_address' => '',
		);

		$has_data = false;
		foreach ( $base_fields as $meta_key => $v ) {
			if ( ! empty( $action_settings[ $meta_key ] ) ) {
				$has_data = true;
				// Update field value
				$base_fields[ $meta_key ] = sanitize_text_field( $action_settings[ $meta_key ] );
			}
		}

		// set contact attributes
		if ( $has_data ) {
			$user_data = array(
				'first_name' => $base_fields['pwp_first_name'],
				'last_name'  => $base_fields['pwp_last_name'],
				'email'      => $base_fields['pwp_email_address'],
			);
		}

		$pwp = personalizewp();
		// Create the Contact if we have data (i.e. is known) or if we're tracking anon users.
		$anon_users  = $pwp->should_anonymise_profiles();
		$track_anon  = $pwp->should_track_anonymous_users();
		$create_user = ! empty( $user_data ) || $track_anon;

		// Get the Contact record, creating it if we have user data to set, or we are tracking anon users.
		$contact = Contact::findByUid( $uid, $create_user );

		// Either we have a previously Anon user, or should have a User that will made known.
		if ( empty( $contact ) ) {
			return $form_data;
		}
		if ( ! empty( $user_data ) ) {
			// Update contact attributes, making the User "known", unless specifically keeping users anon.
			if ( ! $anon_users ) {
				$contact->update( $user_data );
			} else {
				$user_data = [];
				// In which case manually mark the user as 'known', even if we don't know who specifically they are.
				$contact->update( array( 'is_known' => true ) );
			}
		}

		// Only continue if we are tracking anon users or the user is known.
		if ( ! $track_anon && ! $contact->is_known ) {
			return $form_data;
		}

		// Store extra meta for easier rule validation.
		$contact->update_metadata( '_has-completed-form', true );

		// Fixed meta fields.
		$meta_fields = array(
			'marketing_consent',
			'privacy_policy',
			'wp_user_id',
			'wp_user_role',
		);
		// Add all possible extended fields.
		$meta_fields = array_merge( $meta_fields, array_keys( $contact->extended_fields ) );
		// Map the meta fields to the GF fields, populating with submitted data.
		foreach ( $meta_fields as $meta_key ) {
			$full_field = "pwp_{$meta_key}";
			if ( ! empty( $action_settings[ $full_field ] ) ) {
				// Update contact meta from field value.
				$contact->update_metadata( $meta_key, sanitize_text_field( $action_settings[ $full_field ] ) );
			}
		}

		return $form_data;
	}

	/**
	 * Check on all NF forms, adding JS to inject local storage ID when required.
	 *
	 * @param int $form_id Form ID
	 */
	public function check_forms( $form_id ) {

		$this->form_ids[] = $form_id;

		if ( $this->script_added ) {
			return;
		}
		$this->script_added = true;
		add_action( 'wp_footer', array( $this, 'add_form_tracer_js' ), 99 );
	}

	/**
	 * Determines what needs to be processed for the provided submission.
	 *
	 * @since 2.1.0
	 *
	 * @param array $form_data The complete NF Form data Object currently being processed.
	 *
	 * @return void
	 */
	public function process_all_submissions( $form_data ) {

		if ( empty( $form_data['extra']['pwp_tracked_user'] ) || empty( $form_data['extra']['pwp_url'] ) ) {
			return;
		}
		$uid = sanitize_text_field( $form_data['extra']['pwp_tracked_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: already checked during NinjaForms processing
		$url = sanitize_url( $form_data['extra']['pwp_url'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: already checked during NinjaForms processing

		if ( empty( $uid ) || 'pwp_hidden_value' === $uid || empty( $url ) ) {
			// Nothing to track
			return;
		}

		// At this point the User should exist, so ensure we don't create it.
		$contact = Contact::findByUid( $uid, false );
		if ( ! $contact ) {
			// Exit otherwise
			return;
		}

		// Make URL relative if possible.
		$url = str_replace( home_url(), '', $url );
		// Track a form entry, needs no mapping.
		$result = Activity::record(
			$contact->uid,
			array(
				'activity_type' => 'form',
				'url'           => $url,
				'object_id'     => $form_data['form_id'],
				'object_type'   => 'ninjaforms', // Matching the unchanging plugin slug
			)
		);
	}

	/**
	 * Output inline JS that hooks into NF form submission event, populating the form with the Visitor ID.
	 *
	 * As the PWP data is added during form submission, JS works when form is via a placeholder,
	 * and when the PWP data is generated after the point that this JS is added to the page.
	 *
	 * @since 2.1.0
	 *
	 * @return void
	 */
	public function add_form_tracer_js() {
		?>
<script>
(function() {
	const fids = [<?php echo esc_js( join( ', ', $this->form_ids ) ); ?>];
	nfRadio.channel('forms').on('before:submit', function(f) {
		// Make sure the form being submitted is one we want to modify
		if ( -1 === fids.indexOf(+f.id) ) return;
		let pUsr = localStorage.getItem('pwp_tracked_user')||'';
		if ( ! pUsr ) { return; }
		pUsr = JSON.parse( pUsr );
		// Get any existing extra data
		var e = f.get('extra');
		// Merge in new data
		e.pwp_tracked_user = pUsr.id;
		e.pwp_url = document.URL;
		f.set('extra', e);
	});
})();
</script>
		<?php
	}
}
