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 );
}
}