<?php namespace EmailLog\Core;

/**
 * Log's emails sent through `wp_mail`.
 *
 * @package EmailLog\Core
 * @since   2.0
 */
class EmailLogger implements Loadie {

	/**
	 * Load the logger.
	 */
	public function load() {
		add_filter( 'wp_mail', array( $this, 'log_email' ) );
		add_action( 'wp_mail_failed', array( $this, 'on_email_failed' ) );

		/**
		 * These actions are required for logging BuddyPress emails, since BuddyPress does
		 * not use wp_mail for sending emails.
		 *
		 * Support for BuddyPress was added in v2.3.2
		 *
		 * @link https://github.com/sudar/email-log/issues/249
		 */
		add_action( 'bp_send_email_success', array( $this, 'log_buddy_press_email' ), 10, 2 );
		add_action( 'bp_send_email_failure', array( $this, 'log_buddy_press_email' ), 10, 2 );
	}

	/**
	 * Logs email to database.
	 *
	 * @param array $original_mail_info Information about email.
	 *
	 * @return array Information about email.
	 */
	public function log_email( $original_mail_info ) {
		/**
		 * Hook to modify wp_mail contents before Email Log plugin logs.
		 *
		 * @param array $original_mail_info {
		 *     @type string|array $to
		 *     @type string       $subject
		 *     @type string       $message
		 *     @type string|array $headers
		 *     @type string|array $attachment
		 * }
		 *
		 * @since 2.0.0
		 */
		$original_mail_info = apply_filters( 'el_wp_mail_log', $original_mail_info );

		// Sometimes the array passed to the `wp_mail` filter may not contain all the required keys.
		// See https://wordpress.org/support/topic/illegal-string-offset-attachments/.
		$mail_info = wp_parse_args(
			$original_mail_info,
			array(
				'to'          => '',
				'subject'     => '',
				'message'     => '',
				'headers'     => '',
				'attachments' => array(),
			)
		);

		$log = array(
			'to_email'        => \EmailLog\Util\stringify( $mail_info['to'] ),
			'subject'         => $mail_info['subject'],
			'message'         => $mail_info['message'],
			'headers'         => \EmailLog\Util\stringify( $mail_info['headers'], "\n" ),
			'attachment_name' => '',
			'sent_date'       => current_time( 'mysql' ),
			'ip_address'      => '',
			'result'          => 1,
		);

		if ( empty( $log['attachment_name'] ) ) {
			$log['attachments'] = 'false';
		} else {
			$log['attachments'] = 'true';
		}

		/**
		 * Filters the mail info right before inserting on the table.
		 *
		 * Masked fields would use this filter to avoid modifying the original data sent to
		 * `wp_mail() function`
		 *
		 * @param array $log                Email Log that is about to be inserted into db.
		 * @param array $original_mail_info Original mail info that was passed to `wp_mail` filter.
		 *
		 * @since 2.3.2
		 */
		$log = apply_filters( 'el_email_log_before_insert', $log, $original_mail_info );

		$email_log = email_log();
		$email_log->table_manager->insert_log( $log );

		/**
		 * Fires the `el_email_log_inserted` action right after the log is inserted in to DB.
		 *
		 * @since 2.3.0
		 *
		 * @param array $log {
		 *      @type string $to
		 *      @type string $subject
		 *      @type string $message
		 *      @type string $headers
		 *      @type string $attachments
		 *      @type string $attachment_name
		 *      @type string $sent_date
		 *      @type string $ip_address
		 *      @type bool   $result
		 * }
		 */
		do_action( 'el_email_log_inserted', $log );

		return $original_mail_info;
	}

	/**
	 * Updates the failed email in the DB.
	 *
	 * @param \WP_Error $wp_error The error instance.
	 *
	 * @since 2.4.0 Use is_wp_error() to validate the type of $wp_error.
	 * @since 2.3.0
	 *
	 * @see   is_wp_error()
	 * @see   email_log()
	 */
	public function on_email_failed( $wp_error ) {
		if ( ! is_wp_error( $wp_error ) ) {
			return;
		}

		// @see wp-includes/pluggable.php#500
		$mail_error_data = $wp_error->get_error_data( 'wp_mail_failed' );
		$mail_error_message = $wp_error->get_error_message( 'wp_mail_failed' );

		$this->mark_email_log_as_failed( $mail_error_data, $mail_error_message );
	}

	/**
	 * Prepare BuddyPress emails to log into database.
	 *
	 * @since 2.3.2
	 *
	 * @param bool      $status  Mail sent status.
	 * @param \BP_Email $bp_mail Information about email.
	 */
	public function log_buddy_press_email( $status, $bp_mail ) {
		if ( ! class_exists( '\\BP_Email' ) ) {
			return;
		}

		if ( $bp_mail instanceof \BP_Email ) {
			return;
		}

		$log = array(
			'to'      => array_shift( $bp_mail->get_to() )->get_address(),
			'subject' => $bp_mail->get_subject( 'replace-tokens' ),
			'message' => $bp_mail->get_content( 'replace-tokens' ),
			'headers' => $bp_mail->get_headers( 'replace-tokens ' ),
		);

		$this->log_email( $log );

		if ( ! $status ) {
			$this->mark_email_log_as_failed( $log );
		}
	}

	/**
	 * Mark email log as failed.
	 *
	 * @param array  $log           Email Log.
	 * @param string $error_message Error message.
	 *
	 * @since 2.3.2
	 * @since 2.4.0 Store the error message.
	 */
	protected function mark_email_log_as_failed( $log, $error_message = '' ) {
		if ( ! is_array( $log ) ) {
			return;
		}

		if ( ! isset( $log['to'], $log['subject'] ) ) {
			return;
		}

		$email_log = email_log();

		$log_item_id = $email_log->table_manager->fetch_log_id_by_data( $log );

		if ( empty( $log_item_id ) ) {
			return;
		}

		$email_log->table_manager->mark_log_as_failed( $log_item_id, $error_message );
	}
}
