File: /var/www/html/ielts-store/wp-content/plugins/automatewoo/includes/Log.php
<?php
// phpcs:ignoreFile
namespace AutomateWoo;
use AutomateWoo\DataTypes\DataTypes;
use AutomateWoo\Workflows\Factory;
if ( ! defined( 'ABSPATH' ) ) exit;
/**
 * @class Log
 */
class Log extends Abstract_Model_With_Meta_Table {
	/** @var string */
	public $table_id = 'logs';
	/** @var string  */
	public $object_type = 'log';
	/** @var Data_Layer */
	private $data_layer;
	/**
	 * Returns the ID of the model's meta table.
	 *
	 * @return string
	 */
	public function get_meta_table_id() {
		return 'log-meta';
	}
	/**
	 * @param bool|int $id
	 */
	function __construct( $id = false ) {
		if ( $id ) $this->get_by( 'id', $id );
	}
	/**
	 * @param int $workflow_id
	 */
	function set_workflow_id( $workflow_id ) {
		$this->set_prop( 'workflow_id', Clean::id( $workflow_id ) );
	}
	/**
	 * @return int
	 */
	function get_workflow_id() {
		return Clean::id( $this->get_prop( 'workflow_id' ) );
	}
	/**
	 * @param DateTime $date
	 */
	function set_date( $date ) {
		$this->set_date_column( 'date', $date );
	}
	/**
	 * @return DateTime|bool
	 */
	function get_date() {
		return $this->get_date_column( 'date' );
	}
	/**
	 * @param bool $has_errors
	 */
	function set_has_errors( $has_errors ) {
		$this->set_prop( 'has_errors', aw_bool_int( $has_errors ) );
	}
	/**
	 * @return bool
	 */
	function has_errors() {
		return (bool) $this->get_prop( 'has_errors' );
	}
	/**
	 * @param bool $has_blocked_emails
	 */
	function set_has_blocked_emails( $has_blocked_emails ) {
		$this->set_prop( 'has_blocked_emails', absint( (bool) $has_blocked_emails ) );
	}
	/**
	 * @return bool
	 */
	function has_blocked_emails() {
		return (bool) $this->get_prop( 'has_blocked_emails' );
	}
	/**
	 * @param bool $enabled
	 */
	function set_tracking_enabled( $enabled ) {
		$this->set_prop( 'tracking_enabled', aw_bool_int( $enabled ) );
	}
	/**
	 * @return bool
	 */
	function is_tracking_enabled() {
		return (bool) $this->get_prop( 'tracking_enabled' );
	}
	/**
	 * @param bool $enabled
	 */
	function set_conversion_tracking_enabled( $enabled ) {
		$this->set_prop( 'conversion_tracking_enabled', aw_bool_int( $enabled ) );
	}
	/**
	 * @return bool
	 */
	function is_conversion_tracking_enabled() {
		return (bool) $this->get_prop( 'conversion_tracking_enabled' );
	}
	/**
	 * @return bool
	 */
	function is_anonymized() {
		return (bool) $this->get_meta( 'is_anonymized' );
	}
	/**
	 * Records that a trackable message from a workflow was clicked.
	 *
	 * @param string $url
	 */
	public function record_click( $url ) {
		if ( ! $tracking = $this->get_meta('tracking_data') ) {
			$tracking = [];
		}
		$tracking[] = [
			'type' => 'click',
			'url' => $url,
			'date' => current_time( 'mysql', true )
		];
		// clicking requires an open so record one in case images were blocked
		if ( ! $this->has_open_recorded() ) {
			$tracking[] = [
				'type' => 'open',
				'date' => current_time( 'mysql', true )
			];
		}
		$this->update_meta( 'tracking_data', $tracking );
		/**
		 * Fires when a link in a trackable workflow message is clicked.
		 *
		 * @param Workflow $workflow The workflow that sent the message.
		 * @param Log      $log      The record of the workflow running.
		 * @param string   $url      The URL that was clicked
		 */
		do_action( 'automatewoo/workflow/record_click', $this->get_workflow(), $this, $url );
	}
	/**
	 * Records that a trackable message from a workflow was opened.
	 *
	 * Only records an open once i.e. unique opens.
	 */
	public function record_open() {
		if ( $this->has_open_recorded() ) {
			return; // already opened
		}
		if ( ! $tracking = $this->get_meta('tracking_data') ) {
			$tracking = [];
		}
		$tracking[] = [
			'type' => 'open',
			'date' => current_time( 'mysql', true )
		];
		$this->update_meta( 'tracking_data', $tracking );
		/**
		 * Fires for unique opens of a trackable workflow message.
		 *
		 * @param Workflow $workflow The workflow that sent the message.
		 * @param Log      $log      The record of the workflow running.
		 */
		do_action( 'automatewoo/workflow/record_open', $this->get_workflow(), $this );
	}
	/**
	 * @return bool
	 */
	function has_open_recorded() {
		$tracking = $this->get_meta('tracking_data');
		if ( is_array( $tracking ) ) foreach( $tracking as $item ) {
			if ( $item['type'] == 'open') {
				return true;
			}
		}
		return false;
	}
	/**
	 * @return bool
	 */
	function has_click_recorded() {
		$tracking = $this->get_meta('tracking_data');
		if ( is_array( $tracking ) ) foreach( $tracking as $item ) {
			if ( $item['type'] == 'click')
				return true;
		}
		return false;
	}
	/**
	 * @return DateTime|false
	 */
	function get_date_opened() {
		$tracking = $this->get_meta('tracking_data');
		if ( is_array( $tracking ) ) foreach( $tracking as $item ) {
			if ( $item['type'] == 'open') {
				return new DateTime( $item['date'] );
			}
		}
		return false;
	}
	/**
	 * @return DateTime|false
	 */
	function get_date_clicked() {
		$tracking = $this->get_meta('tracking_data');
		if ( is_array( $tracking ) ) foreach( $tracking as $item ) {
			if ( $item['type'] == 'click') {
				return new DateTime( $item['date'] );
			}
		}
		return false;
	}
	/**
	 * Add a note.
	 *
	 * @param string $note
	 */
	function add_note( $note ) {
		$notes = $this->get_notes();
		$notes[] = $note;
		$this->update_notes( $notes );
	}
	/**
	 * Get the log notes.
	 *
	 * @since 4.4.0
	 *
	 * @return array
	 */
	function get_notes() {
		$notes = $this->get_meta( 'notes' );
		if ( ! is_array( $notes ) ) {
			return [];
		}
		else {
			return Clean::recursive( $notes );
		}
	}
	/**
	 * Update the log notes.
	 *
	 * @since 4.4.0
	 *
	 * @param array $notes
	 */
	function update_notes( $notes ) {
		$notes = Clean::recursive( $notes );
		$this->update_meta( 'notes', $notes );
	}
	/**
	 * Returns the workflow without a data layer
	 * @return Workflow
	 */
	function get_workflow() {
		return Factory::get( $this->get_workflow_id() );
	}
	/**
	 * @param string $output - array|object this for backwards compatibility
	 * @return Data_Layer|array
	 */
	function get_data_layer( $output = 'array' ) {
		if ( ! isset( $this->data_layer ) ) {
			if ( $compressed = $this->get_compressed_data_layer() ) {
				$this->data_layer = $this->decompress_data_layer( $compressed );
			}
			else {
				$this->data_layer = new Data_Layer();
			}
		}
		if ( $output == 'array' ) {
			return $this->data_layer->get_raw_data();
		}
		return $this->data_layer;
	}
	/**
	 * Fetches the data layer from log meta, but does not decompress
	 * Uses the the supplied_data_items field on the workflows trigger
	 *
	 * @return array|false
	 */
	private function get_compressed_data_layer() {
		if ( ! $workflow = $this->get_workflow() )
			return false; // workflow must be set
		if ( ! $this->exists )
			return false; // log must be saved
		if ( ! $trigger = $workflow->get_trigger() )
			return false; // need a trigger
		$data_layer = [];
		$supplied_items = $trigger->get_supplied_data_items();
		// when anonymized log is converted to guest
		if ( $this->is_anonymized() ) {
			$supplied_items[] = 'guest';
		}
		foreach ( $supplied_items as $data_type_id ) {
			$data_item_value = $this->get_compressed_data_item( $data_type_id, $trigger->get_supplied_data_items() );
			if ( $data_item_value !== false ) {
				$data_layer[ $data_type_id ] = $data_item_value;
			}
		}
		return $data_layer;
	}
	/**
	 * @param $data_type_id
	 * @param array $supplied_data_items
	 * @return string|false
	 */
	private function get_compressed_data_item( $data_type_id, $supplied_data_items ) {
		if ( DataTypes::is_non_stored_data_type( $data_type_id ) ) {
			return false; // storage not required
		}
		// user requires special logic when related to an order
		if ( $data_type_id === 'user' && in_array( 'order', $supplied_data_items ) ) {
			return 0; // get user data from the order when decompressing
		}
		$storage_key = Logs::get_data_layer_storage_key( $data_type_id );
		if ( ! $storage_key )
			return false;
		return Clean::string( $this->get_meta( $storage_key ) );
	}
	/**
	 * @param array $compressed_data_layer
	 * @return Data_Layer
	 */
	private function decompress_data_layer( $compressed_data_layer ) {
		$data = [];
		if ( is_array( $compressed_data_layer ) ) foreach ( $compressed_data_layer as $data_type_id => $compressed_item ) {
			if ( $data_type = DataTypes::get( $data_type_id ) ) {
				$data[$data_type_id] = $data_type->decompress( $compressed_item, $compressed_data_layer );
			}
		}
		return new Data_Layer( $data );
	}
	/**
	 * Stores a data layer in log meta
	 * @param Data_Layer $data_layer
	 */
	function store_data_layer( $data_layer ) {
		if ( ! $this->exists )
			return; // log must be saved before meta can be added
		foreach ( $data_layer->get_raw_data() as $data_type_id => $data_item ) {
			$this->store_data_item( $data_type_id, $data_item );
		}
	}
	/**
	 * @param $data_type_id
	 * @param $data_item
	 */
	private function store_data_item( $data_type_id, $data_item ) {
		$data_type = DataTypes::get( $data_type_id );
		if (
			! $data_type ||
			! $data_type->validate( $data_item ) ||
			DataTypes::is_non_stored_data_type( $data_type_id )
		) {
			return;
		}
		// special logic for users who are actually guests
		if ( $data_type_id === 'user' && $data_item->ID === 0 ) {
			$storage_key = 'guest_email';
			$storage_value = $data_item->user_email;
		}
		else {
			$storage_key = Logs::get_data_layer_storage_key( $data_type_id );
			$storage_value = Logs::get_data_layer_storage_value( $data_type_id, $data_item );
		}
		if ( $storage_key ) {
			$this->update_meta( $storage_key, $storage_value );
		}
	}
	/**
	 * Delete the log and clear related conversion order meta
	 */
	function delete() {
		// delete conversion records for the log
		$query = new \WP_Query([
			'post_type' => 'shop_order',
			'post_status' => 'any',
			'posts_per_page' => -1,
			'no_found_rows' => true,
			'fields' => 'ids',
			'meta_query' => [
				[
					'key' => '_aw_conversion_log',
					'value' => $this->get_id()
				]
			]
		]);
		$converted_orders = $query->posts;
		if ( $converted_orders ) {
			foreach ( $converted_orders as $order_id ) {
				$order = wc_get_order( $order_id );
				if ( $order ) {
					$order->delete_meta_data( '_aw_conversion' );
					$order->delete_meta_data( '_aw_conversion_log' );
					$order->save();
				}
			}
		}
		$this->clear_cached_data();
		parent::delete();
	}
	/**
	 *
	 */
	function save() {
		$this->clear_cached_data();
		parent::save();
	}
	function clear_cached_data() {
		if ( ! $this->get_workflow_id() )
			return;
		Cache::delete_transient( 'times_run/workflow=' . $this->get_workflow_id() );
	}
	/**
	 * Reruns the workflow skipping validation
	 * @return Log|bool - the newly created log
	 */
	function rerun() {
		$workflow = $this->get_workflow();
		$workflow->maybe_run( $this->get_data_layer( 'object' ), true, true );
		$log = $workflow->get_current_log();
		if ( $log ) {
			$log->add_note( __( 'This log was created from manual workflow re-run.', 'automatewoo' ) );
			return $log;
		}
		return false;
	}
}