HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/globfdxw/public_html/wp-content/plugins/wpforms-pdf/src/Notifications/Process.php
<?php

// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpDeprecationInspection */

namespace WPFormsPDF\Notifications;

use WPForms_WP_Emails; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement
use WPForms\Emails\Mailer;
use WPForms\Emails\Notifications;
use WPFormsPDF\Helpers;
use WPFormsPDF\Templates\Templates;
use WPFormsPDF\PDF\Renderer;
use WPFormsPDF\Storage;
use WPFormsPDF\PDF\Generator;
use WPForms\Emails\Templates\General as GeneralEmailTemplate;
use WPFormsPDF\Access\PDF;

/**
 * Process class.
 *
 * Handles the integration with WPForms notifications system.
 *
 * @since 1.0.0
 */
class Process {

	/**
	 * Current entry ID.
	 *
	 * @since 1.0.0
	 *
	 * @var int
	 */
	private $entry_id = 0;

	/**
	 * Current entry fields data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $entry_fields = [];

	/**
	 * Current form ID.
	 *
	 * @since 1.0.0
	 *
	 * @var int
	 */
	private $form_id = 0;

	/**
	 * Current form data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $form_data = [];

	/**
	 * Flag whether to process the notification.
	 *
	 * @since 1.0.0
	 *
	 * @var bool
	 */
	private $should_process_email;

	/**
	 * Flag whether to catch the notification content.
	 *
	 * @since 1.0.0
	 *
	 * @var bool
	 */
	private $should_catch_content;

	/**
	 * Current notification settings.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $notification_settings;

	/**
	 * PDF settings applicable to the current notification.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $applicable_pdfs;

	/**
	 * Current PDF id.
	 *
	 * @since 1.0.0
	 *
	 * @var int
	 */
	private $pdf_id;

	/**
	 * PDFs data applicable to the current notification.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $pdfs;

	/**
	 * Fields class instance.
	 *
	 * @since 1.0.0
	 *
	 * @var Fields
	 */
	private $fields_obj;

	/**
	 * Renderer class instance.
	 *
	 * @since 1.0.0
	 *
	 * @var Renderer
	 */
	private $renderer;

	/**
	 * Helpers.
	 *
	 * @since 1.2.0
	 *
	 * @var Helpers
	 */
	private $helpers;

	/**
	 * Initialize class.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		$this->helpers = wpforms_pdf()->helpers;

		$this->hooks();
	}

	/**
	 * Register hooks.
	 *
	 * @since 1.0.0
	 */
	private function hooks(): void {

		add_filter(
			'wpforms_emails_templates_general_get_content_parts',
			[ $this, 'filter_get_content_parts' ],
			10,
			2
		);

		add_filter( 'wpforms_entry_email_process', [ $this, 'filter_entry_email_process' ], 10, 5 );
		add_action( 'wpforms_email_send_before', [ $this, 'process_email' ] );
		add_action( 'wpforms_emails_mailer_send_before', [ $this, 'process_email' ] );
	}

	/**
	 * Hook into each notification.
	 *
	 * @since 1.0.0
	 *
	 * @param bool|mixed $enabled         Whether to send the email.
	 * @param array      $fields          List of fields.
	 * @param array      $form_data       Form data and settings.
	 * @param int        $notification_id Notification ID.
	 * @param string     $context         In which context this email is sent.
	 *
	 * @return bool
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function filter_entry_email_process( $enabled, array $fields, array $form_data, int $notification_id, string $context ): bool { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$enabled = (bool) $enabled;

		$this->should_process_email  = false;
		$this->pdfs                  = [];
		$this->entry_fields          = $fields;
		$this->form_data             = $form_data;
		$this->form_id               = absint( $form_data['id'] ?? 0 );
		$this->notification_settings = $form_data['settings']['notifications'][ $notification_id ] ?? [];

		if ( empty( $this->notification_settings ) ) {
			return $enabled;
		}

		// Find all PDFs that apply to this notification.
		$this->applicable_pdfs = $this->get_applicable_pdfs( $notification_id );

		if ( empty( $this->applicable_pdfs ) ) {
			return $enabled;
		}

		if ( ! $this->helpers->is_valid_license() ) {
			$this->helpers->log(
				[
					'error'        => 'PDF was not attached to notification due to invalid license.',
					'notification' => $this->notification_settings,
					'form_data'    => $form_data,
					'entry_fields' => $fields,
				],
				'error'
			);

			return $enabled;
		}

		$this->process_applicable_pdfs( $notification_id, $fields, $form_data );

		return $enabled;
	}

	/**
	 * Process all applicable PDFs for the current notification.
	 *
	 * @since 1.2.0
	 *
	 * @param int   $notification_id Notification ID.
	 * @param array $fields          List of fields.
	 * @param array $form_data       Form data and settings.
	 */
	private function process_applicable_pdfs( int $notification_id, array $fields, array $form_data ): void {

		foreach ( $this->applicable_pdfs as $pdf_id => $pdf ) {
			$this->pdf_id          = $pdf_id;
			$this->pdfs[ $pdf_id ] = [
				'settings' => $pdf,
				'template' => wpforms_pdf()->templates->get_template( $pdf['template_style'], $pdf['theme'] ),
				'text'     => [],
			];

			$this->notification_settings['id'] = $notification_id;

			$this->should_process_email = true;
			$this->should_catch_content = true;

			$this->prepare_content( $fields, $form_data );
		}
	}

	/**
	 * Get PDFs applicable to the current notification.
	 *
	 * @since 1.0.0
	 *
	 * @param int $notification_id Current notification ID.
	 *
	 * @return array Array of applicable PDF settings.
	 */
	private function get_applicable_pdfs( int $notification_id ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( empty( $this->form_data['settings']['pdfs'] ) ) {
			return [];
		}

		$applicable_pdfs = [];

		foreach ( $this->form_data['settings']['pdfs'] as $pdf_id => $pdf ) {

			// If PDF is not enabled, skip it.
			if ( empty( $pdf['enable'] ) ) {
				continue;
			}

			$pdf['notifications'] = is_array( $pdf['notifications'] ) ? $pdf['notifications'] : [ (int) $pdf['notifications'] ];

			// If a PDF is not set to be attached to this notification, skip it.
			if ( ! empty( $pdf['notifications'] ) && ! in_array( $notification_id, $pdf['notifications'], true ) ) {
				continue;
			}

			// Check if PDF should be processed based on conditional logic.
			if ( ! $this->should_process_pdf_by_conditional_logic( $this->entry_fields, $this->form_data, $pdf_id ) ) {
				continue;
			}

			$applicable_pdfs[ $pdf_id ] = $pdf;
		}

		return $applicable_pdfs;
	}

	/**
	 * Determine if PDF should be processed based on conditional logic.
	 *
	 * @since 1.0.0
	 *
	 * @param array $fields    List of submitted fields.
	 * @param array $form_data Form data and settings.
	 * @param int   $pdf_id    PDF ID.
	 *
	 * @return bool
	 */
	private function should_process_pdf_by_conditional_logic( array $fields, array $form_data, int $pdf_id ): bool {

		$settings = $form_data['settings'];

		// If there's no PDF conditional logic configuration, process the PDF.
		if (
			empty( $settings['pdfs'][ $pdf_id ]['conditional_logic'] ) ||
			empty( $settings['pdfs'][ $pdf_id ]['conditional_type'] ) ||
			empty( $settings['pdfs'][ $pdf_id ]['conditionals'] )
		) {
			return true;
		}

		$process = wpforms_conditional_logic()->process( $fields, $form_data, $settings['pdfs'][ $pdf_id ]['conditionals'] );

		if ( $settings['pdfs'][ $pdf_id ]['conditional_type'] === 'stop' ) {
			$process = ! $process;
		}

		// If preventing PDF generation, log it.
		if ( ! $process ) {
			$this->helpers->log(
				[
					'message'      => 'PDF was not attached to notification due to conditional logic.',
					'pdf_id'       => $pdf_id,
					'notification' => $this->notification_settings,
					'form_data'    => $form_data,
					'entry_fields' => $fields,
				]
			);
		}

		return $process;
	}

	/**
	 * Prepare the content for the PDF file.
	 *
	 * We should render the entry content using our custom settings
	 *
	 * @since 1.0.0
	 *
	 * @param array $fields    Fields data.
	 * @param array $form_data Form data.
	 *
	 * @return string
	 **/
	public function prepare_content( array $fields, array $form_data ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( ! $this->should_process_email || ! $this->should_catch_content || empty( $this->notification_settings ) ) {
			return '';
		}

		$template       = $this->pdfs[ $this->pdf_id ]['template'];
		$notification   = $this->notification_settings;
		$email_template = $template['email_style'] ?? Templates::DEFAULT_EMAIL_STYLE;
		$category       = $template['category'] ?? '';

		$sender_name  = $this->get_notification_element( $notification, 'sender_name', get_bloginfo( 'name' ) );
		$sender_email = $this->get_notification_element( $notification, 'sender_email', get_option( 'admin_email' ) );

		// Default message.
		$message = '{all_fields}';

		// If it's a notification, use the message from the notification settings.
		if ( $category === 'notification' ) {
			$message = $this->get_notification_element( $notification, 'message', $message );
		}

		// Create a new email.
		$emails      = ( new Notifications() )->init( $email_template, 'pdf' );
		$process_obj = wpforms()->obj( 'process' );

		$emails->__set( 'form_data', $form_data );
		$emails->__set( 'fields', $fields );
		$emails->__set( 'from_name', $sender_name );
		$emails->__set( 'entry_id', $process_obj->entry_id ?? '' );

		// Create the new fields object if necessary.
		$this->fields_obj = $this->fields_obj ?? new Fields();

		$this->fields_obj->init( $this->entry_fields, $this->form_data, $template );

		// Process the email template.
		// This will replace the {all_fields} smart tag with the entry content.
		// Then, the result content we will get in the filter_get_content_parts callback.
		$emails->process_email_template( $message );

		$this->fields_obj->disable_field_processing();

		$this->pdfs[ $this->pdf_id ]['text']['sender_name']  = $sender_name;
		$this->pdfs[ $this->pdf_id ]['text']['sender_email'] = $sender_email;

		return $emails->get_message();
	}

	/**
	 * Get notification element value or return default.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $notification  Notification settings.
	 * @param string $key           Key to retrieve from the notification.
	 * @param mixed  $default_value Default value.
	 *
	 * @return mixed
	 */
	private function get_notification_element( array $notification, string $key, $default_value ) {

		return ! empty( $notification[ $key ] ) ? $notification[ $key ] : $default_value;
	}

	/**
	 * Filter email content parts.
	 *
	 * This is a callback for the wpforms_emails_templates_general_get_content_parts filter.
	 * Captures the email content parts for later use in PDF generation.
	 *
	 * @since 1.0.0
	 *
	 * @param array                $parts Content parts.
	 * @param GeneralEmailTemplate $email Email object.
	 *
	 * @return array Content parts.
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function filter_get_content_parts( array $parts, GeneralEmailTemplate $email ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		if (
			! $this->should_process_email ||
			! empty( $this->pdfs[ $this->pdf_id ]['text']['all_fields'] )
		) {
			return $parts;
		}

		// Get a clean body.
		$email_body    = $email->get_args( 'body' );
		$email_message = $email_body['message'] ?? '';

		if ( ! empty( $email_message ) ) {
			// Strip all smart-tag row wrappers to get the clean inner content.
			$email_message = preg_replace( '#<tr class="smart-tag">\s*<td class="field-name field-value" colspan="2">(.*?)</td>\s*</tr>#s', '$1', $email_message );
		}

		// In case the message contains any table tag use body with wrapped table tag.
		if ( preg_match( '/<(table|thead|tfoot|tbody|tr|td|th)\b[^>]*>/i', $email_message ) ) {
			$body_message = $parts['body'] ?? '';
		} else {
			$body_message = $email_message ?? '';
		}

		$this->pdfs[ $this->pdf_id ]['text']['all_fields'] = $body_message;

		return $parts;
	}

	/**
	 * Process email before sending.
	 *
	 * This is a callback for the wpforms_emails_mailer_send_before action.
	 *
	 * @since 1.0.0
	 *
	 * @param Mailer|WPForms_WP_Emails $mailer Mailer object.
	 */
	public function process_email( $mailer ): void {

		$pdf_dir = $this->get_pdf_dir( $mailer );

		// An empty directory means we can't proceed.
		if ( empty( $pdf_dir ) ) {
			return;
		}

		foreach ( $this->applicable_pdfs as $pdf_id => $pdf_settings ) {
			$pdf_data = $this->pdfs[ $pdf_id ] ?? null;

			if ( empty( $pdf_data ) ) {
				continue;
			}

			$this->pdf_id = $pdf_id;

			$pdf_file = $this->generate_pdf( $pdf_data, $pdf_dir );

			// If access restrictions are enabled, insert a link to the email body instead of an attachment.
			if ( PDF::is_file_protected( $pdf_settings ) ) {
				$this->insert_pdf_link_to_email_body( $mailer, $pdf_settings, $pdf_id );
			} else {
				$this->attach_pdf( $pdf_id, $pdf_file, $mailer );
			}
		}

		$this->should_process_email = false;
	}

	/**
	 * Insert a PDF download link at the end of the email body.
	 *
	 * @since 1.0.0
	 *
	 * @param Mailer|WPForms_WP_Emails $mailer       Mailer object.
	 * @param array                    $pdf_settings PDF settings.
	 * @param int                      $pdf_id       PDF ID.
	 *
	 * @noinspection HtmlUnknownTarget
	 * @noinspection PhpUnusedParameterInspection
	 */
	private function insert_pdf_link_to_email_body( $mailer, array $pdf_settings, int $pdf_id ): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		$form_id   = $this->form_id;
		$entry_id  = $this->entry_id;
		$link      = PDF::generate_pdf_download_url( $form_id, $entry_id, $pdf_id );
		$file_name = self::get_pdf_file_name( $pdf_settings, $this->form_data, $this->entry_fields, $this->entry_id );

		$link = sprintf(
			'<a href="%1$s">%2$s %3$s</a><br>',
			esc_url( $link ),
			esc_html__( 'Download', 'wpforms-pdf' ),
			$file_name
		);

		add_filter(
			'wpforms_emails_send_email_data',
			static function ( $data ) use ( $link ) {

				$data['message']  = $data['message'] ?? '';
				$data['message'] .= $link;

				return $data;
			},
			100
		);
	}

	/**
	 * Get PDF directory.
	 *
	 * @since 1.0.0
	 *
	 * @param Mailer|WPForms_WP_Emails $mailer Mailer.
	 *
	 * @return string
	 */
	private function get_pdf_dir( $mailer ): string {

		if ( ! $this->should_process_email || empty( $this->pdfs ) ) {
			return '';
		}

		// Extract an entry ID.
		if ( ! empty( $mailer->entry_id ) ) {
			$this->entry_id = absint( $mailer->entry_id );
		}

		return Storage::get_dir( $this->form_id, $this->entry_id );
	}

	/**
	 * Generate PDF file.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $pdf_data PDF data.
	 * @param string $pdf_dir  PDF directory.
	 *
	 * @return string
	 */
	private function generate_pdf( array $pdf_data, string $pdf_dir ): string {

		$theme_slug    = $pdf_data['template']['theme']['slug'] ?? '';
		$template_slug = $pdf_data['template']['slug'] ?? '';

		if ( empty( $theme_slug ) || empty( $template_slug ) ) {
			return '';
		}

		// Create the renderer if necessary.
		$this->renderer = $this->renderer ?? new Renderer();

		// Render HTML.
		$html = $this->renderer
			->init_pdf_settings( $this->pdf_id, $this->form_data, $this->entry_fields, $this->entry_id )
			->render_html( $pdf_data['text'] );

		$images    = $this->renderer->get_images_data();
		$file_name = self::get_pdf_file_name( $pdf_data['settings'], $this->form_data, $this->entry_fields, $this->entry_id );
		$file_path = $pdf_dir . $file_name;

		// Extra data that will be passed to the API.
		$extra = [
			'form_id' => $this->form_id,
		];

		$is_generated = $this->do_generate_pdf( $pdf_data['settings'], $html, $images, $file_path, $template_slug, $extra );

		if ( ! $is_generated ) {
			$this->helpers->log(
				[
					'message'  => 'PDF generation failed.',
					'file'     => $file_path,
					'pdf_data' => $pdf_data,
					'html'     => $html,
				],
				'error'
			);

			return '';
		}

		return $file_path;
	}

	/**
	 * Generate PDF from HTML content.
	 *
	 * @since 1.0.0
	 *
	 * @param array       $settings      PDF settings.
	 * @param string      $html          HTML content to convert to PDF.
	 * @param array       $images        Images data.
	 * @param string      $file_path     File path where the PDF will be saved.
	 * @param string|null $template_slug PDF template slug.
	 * @param array       $extra         Extra data that will be passed to the API.
	 *
	 * @return bool
	 */
	private function do_generate_pdf( array $settings, string $html, array $images, string $file_path, ?string $template_slug = null, array $extra = [] ): bool {

		// Generate PDF from given HTML.
		$generator = new Generator(
			[
				'template'    => $template_slug,
				'paper_size'  => $settings['paper_size'] ?? null,
				'orientation' => $settings['orientation'] ?? null,
			],
			$extra
		);

		return $generator->generate_from_html( $html, $images )->save( $file_path );
	}

	/**
	 * Maybe attach PDF to the email.
	 *
	 * @since 1.0.0
	 *
	 * @param int                      $pdf_id   PDF file Id.
	 * @param string                   $pdf_file PDF file path.
	 * @param Mailer|WPForms_WP_Emails $mailer   Mailer object.
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private function attach_pdf( int $pdf_id, string $pdf_file, $mailer ): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks, Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

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

		add_filter(
			'wpforms_emails_send_email_data',
			function ( $data ) use ( $pdf_id, $pdf_file ) {

				// Skip attaching if the PDF is not applicable to the current notification.
				if ( ! array_key_exists( $pdf_id, $this->applicable_pdfs ) ) {
					return $data;
				}

				$data['attachments']   = (array) ( $data['attachments'] ?? [] );
				$data['attachments'][] = $pdf_file;

				return $data;
			},
			100
		);
	}

	/**
	 * Get the PDF file name.
	 *
	 * @since 1.0.0
	 *
	 * @param array $pdf_settings PDF settings.
	 * @param array $form_data    Form data.
	 * @param array $entry_fields Entry fields.
	 * @param int   $entry_id     Entry ID.
	 *
	 * @return string
	 */
	public static function get_pdf_file_name( array $pdf_settings, array $form_data, array $entry_fields, int $entry_id ): string {

		$file_name = $pdf_settings['file_name'] ?? '';
		$file_name = empty( $file_name ) ? 'Entry for {form_name}.pdf' : $file_name;
		$file_name = substr( $file_name, -4, 4 ) !== '.pdf' ? $file_name . '.pdf' : $file_name;
		$file_name = wpforms_process_smart_tags( $file_name, $form_data, $entry_fields, (string) $entry_id, 'wpforms-pdf-file-name' );
		$file_name = sanitize_file_name( html_entity_decode( $file_name ) );

		// Remove chars that are not allowed in addition to chars defined in sanitize_file_name.
		// ^ - with carat the URL to download a PDF file will be broken.
		$special_chars = [ '^' ];
		$file_name     = str_replace( $special_chars, '', $file_name );

		/**
		 * Filter PDF file name.
		 *
		 * @since 1.0.0
		 *
		 * @param string $file_name    PDF file name.
		 * @param array  $pdf_settings PDF settings.
		 * @param array  $form_data    Form data.
		 * @param array  $entry_fields Entry fields.
		 * @param string $entry_id     Entry ID.
		 */
		return apply_filters( 'wpforms_pdf_notifications_process_file_name', $file_name, $pdf_settings, $form_data, $entry_fields, $entry_id );
	}
}