From 2038e4e754022723617a06c5ac2ae20c0973e7e2 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Thu, 13 Jun 2024 15:01:50 +1000 Subject: [PATCH] Add temporary PDF cache --- api.php | 29 +- src/Controller/Controller_Install.php | 2 + src/Controller/Controller_PDF.php | 311 +++++++++----- src/Controller/Controller_Pdf_Queue.php | 18 +- .../Controller_Upgrade_Routines.php | 5 + src/Helper/Helper_Misc.php | 7 +- src/Helper/Helper_PDF.php | 79 +--- src/Model/Model_PDF.php | 391 +++++++++++------- src/Model/Model_Shortcodes.php | 4 +- src/Statics/Cache.php | 103 +++++ src/Statics/Debug.php | 64 +++ src/Statics/Queue_Callbacks.php | 3 + src/View/View_PDF.php | 132 +++--- src/deprecated.php | 2 +- .../confirmation-shortcode.test.js | 6 +- .../Controller/Test_Controller_Pdf_Queue.php | 26 +- tests/phpunit/unit-tests/test-pdf.php | 70 ++-- .../unit-tests/test-slow-pdf-processes.php | 60 ++- 18 files changed, 816 insertions(+), 496 deletions(-) create mode 100644 src/Statics/Cache.php create mode 100644 src/Statics/Debug.php diff --git a/api.php b/api.php index 58b9bd13a..2da9fbf2d 100644 --- a/api.php +++ b/api.php @@ -448,19 +448,21 @@ public static function delete_plugin_option( $key ) { } /** - * When provided the Gravity Form entry ID and PDF ID, this method will correctly generate the PDF, save it to disk, - * trigger appropriate actions and return the absolute path to the PDF. + * Generate a PDF, save it to disk, and return the absolute path to the document * * See https://docs.gravitypdf.com/v6/developers/api/create_pdf/ for more information about this method * - * @param integer $entry_id The Gravity Form entry ID - * @param string $pdf_id The Gravity PDF ID number (the pid number in the URL when viewing a setting in the admin area) + * @param int $entry_id The Gravity Form entry ID + * @param string $pdf_id The Gravity PDF ID number (the pid number in the URL when viewing a setting in the admin area) + * @param bool $bypass_cache Force a new PDF to be generated * - * @return mixed Return the full path to the PDF, or a WP_Error on failure + * @return string|WP_Error Return the full path to the PDF, or a WP_Error on failure * * @since 4.0 + * @since 6.12 All PDFs are cached on disk for ~1 hour, but are auto-purged if the form, entry, or PDF settings change + * Re-running the method will return the cached PDF if it exists, unless $bypass_cache = true */ - public static function create_pdf( $entry_id, $pdf_id ) { + public static function create_pdf( $entry_id, $pdf_id, $bypass_cache = false ) { $form_class = static::get_form_class(); @@ -478,16 +480,17 @@ public static function create_pdf( $entry_id, $pdf_id ) { return new WP_Error( 'invalid_pdf_setting', esc_html__( 'Could not located the PDF Settings. Ensure you pass in a valid PDF ID.', 'gravity-forms-pdf-extended' ) ); } - $pdf = static::get_mvc_class( 'Model_PDF' ); - $form = $form_class->get_form( $entry['form_id'] ); + if ( $bypass_cache ) { + add_filter( 'gfpdf_override_pdf_bypass', '__return_true' ); + } + + /** @var \GFPDF\Model\Model_PDF $pdf */ + $pdf = static::get_mvc_class( 'Model_PDF' ); + $path_to_pdf = $pdf->generate_and_save_pdf( $entry, $setting ); - add_filter( 'gfpdf_override_pdf_bypass', '__return_true' ); - do_action( 'gfpdf_pre_generate_and_save_pdf', $form, $entry, $setting ); - $filename = $pdf->generate_and_save_pdf( $entry, $setting ); - do_action( 'gfpdf_post_generate_and_save_pdf', $form, $entry, $setting ); remove_filter( 'gfpdf_override_pdf_bypass', '__return_true' ); - return $filename; + return $path_to_pdf; } /** diff --git a/src/Controller/Controller_Install.php b/src/Controller/Controller_Install.php index 8dfca8a50..2c50faccb 100644 --- a/src/Controller/Controller_Install.php +++ b/src/Controller/Controller_Install.php @@ -202,6 +202,8 @@ public function check_install_status() { * Determine if we should be saving the PDF settings * * @since 4.0 + * + * @deprecated 6.0 */ public function maybe_uninstall() { _doing_it_wrong( __METHOD__, 'This method has been moved to Controller_Uninstall::uninstall_addon()', '6.0' ); diff --git a/src/Controller/Controller_PDF.php b/src/Controller/Controller_PDF.php index a5f2ed6e1..d489f584f 100644 --- a/src/Controller/Controller_PDF.php +++ b/src/Controller/Controller_PDF.php @@ -10,7 +10,9 @@ use GFPDF\Helper\Helper_Interface_Actions; use GFPDF\Helper\Helper_Interface_Filters; use GFPDF\Helper\Helper_Misc; +use GFPDF\Helper\Helper_PDF; use GFPDF\Model\Model_PDF; +use GFPDF\Statics\Debug; use GFPDF\View\View_PDF; use Psr\Log\LoggerInterface; use SiteGround_Optimizer\Minifier\Minifier; @@ -30,9 +32,12 @@ * Controller_PDF * Handles the PDF display and authentication * + * @property View_PDF $view + * @property Model_PDF $model + * * @since 4.0 */ -class Controller_PDF extends Helper_Abstract_Controller implements Helper_Interface_Actions, Helper_Interface_Filters { +class Controller_PDF extends Helper_Abstract_Controller { /** * Holds the abstracted Gravity Forms API specific to Gravity PDF @@ -89,69 +94,56 @@ public function __construct( Helper_Abstract_Model $model, Helper_Abstract_View } /** - * Initialise our class defaults - * * @return void * @since 4.0 - * */ public function init() { - /* - * Tell Gravity Forms to add our form PDF settings pages - */ $this->add_actions(); $this->add_filters(); /* Add scheduled tasks */ if ( ! wp_next_scheduled( 'gfpdf_cleanup_tmp_dir' ) ) { - wp_schedule_event( time(), 'twicedaily', 'gfpdf_cleanup_tmp_dir' ); + wp_schedule_event( time(), 'hourly', 'gfpdf_cleanup_tmp_dir' ); } } /** - * Apply any actions needed for the settings page - * * @return void * @since 4.0 - * */ public function add_actions() { /* Process PDF if needed */ add_action( 'parse_request', [ $this, 'process_legacy_pdf_endpoint' ] ); /* legacy PDF endpoint */ add_action( 'parse_request', [ $this, 'process_pdf_endpoint' ] ); /* new PDF endpoint */ - /* Allow custom PDF tags / CSS */ + /* Set up pre- and post-generation PDF hooks */ add_action( 'gfpdf_pre_pdf_generation', [ $this, 'add_pre_pdf_hooks' ] ); add_action( 'gfpdf_post_pdf_generation', [ $this, 'remove_pre_pdf_hooks' ] ); + /* Set up pre generation hooks when streaming PDF to the browser */ + $add_pre_view_or_download_pdf_hooks = function( $form, $entry, $settings ) { + $this->add_pre_view_or_download_pdf_hooks( $form, $entry, $settings ); + }; + + add_action( 'gfpdf_view_or_download_pdf', $add_pre_view_or_download_pdf_hooks, 10, 3 ); + /* Display PDF links in Gravity Forms Admin Area */ add_action( 'gform_entries_first_column_actions', [ $this->model, 'view_pdf_entry_list' ], 10, 4 ); add_action( 'gravityflow_workflow_detail_sidebar', [ $this->model, 'view_pdf_gravityflow_inbox' ], 10, 4 ); - /* Add save PDF actions */ + /* Add hooks to save PDF to disk, or run right after a PDF is saved to disk */ add_action( 'gform_after_submission', [ $this->model, 'maybe_save_pdf' ], 10, 2 ); add_action( 'gfpdf_post_pdf_generation', [ $this->model, 'trigger_post_save_pdf' ], 10, 4 ); - /* Clean-up actions */ - add_action( 'gform_after_submission', [ $this->model, 'cleanup_pdf' ], 9999, 2 ); - add_action( 'gform_after_update_entry', [ $this->model, 'cleanup_pdf_after_submission' ], 9999, 2 ); + /* Scheduled clean-up actions */ add_action( 'gfpdf_cleanup_tmp_dir', [ $this->model, 'cleanup_tmp_dir' ] ); - /* Add Gravity Perk Population Anything Support */ - if ( function_exists( 'gp_populate_anything' ) ) { - add_action( 'gfpdf_pre_pdf_generation', [ $this->model, 'enable_gp_populate_anything' ] ); - add_action( 'gfpdf_pre_pdf_generation_output', [ $this->model, 'disable_gp_populate_anything' ] ); - - /* register preferred hydration method */ - add_filter( 'gfpdf_current_form_object', [ $this->model, 'gp_populate_anything_hydrate_form' ], 5, 2 ); - - /* remove legacy filters */ - if ( class_exists( '\GPPA_Compatibility_GravityPDF' ) ) { - $gp_pdf_compat = \GPPA_Compatibility_GravityPDF::get_instance(); - remove_action( 'gfpdf_pre_view_or_download_pdf', [ $gp_pdf_compat, 'hydrate_form_hook_for_pdf_view_or_download' ] ); - remove_action( 'gfpdf_pre_generate_and_save_pdf_notification', [ $gp_pdf_compat, 'hydrate_form_hook' ] ); - remove_action( 'gfpdf_pre_generate_and_save_pdf', [ $gp_pdf_compat, 'hydrate_form_hook' ] ); - } + /* Remove legacy Gravity Perk Population Anything Support */ + if ( class_exists( '\GPPA_Compatibility_GravityPDF' ) ) { + $gp_pdf_compat = \GPPA_Compatibility_GravityPDF::get_instance(); + remove_action( 'gfpdf_pre_view_or_download_pdf', [ $gp_pdf_compat, 'hydrate_form_hook_for_pdf_view_or_download' ] ); + remove_action( 'gfpdf_pre_generate_and_save_pdf_notification', [ $gp_pdf_compat, 'hydrate_form_hook' ] ); + remove_action( 'gfpdf_pre_generate_and_save_pdf', [ $gp_pdf_compat, 'hydrate_form_hook' ] ); } /* Add Legal Signature support */ @@ -163,11 +155,8 @@ public function add_actions() { } /** - * Apply any filters needed for the settings page - * * @return void * @since 4.0 - * */ public function add_filters() { /* PDF authentication middleware */ @@ -188,16 +177,8 @@ public function add_filters() { add_filter( 'gfpdf_field_middleware', [ $this->model, 'field_middle_page' ], 10, 5 ); add_filter( 'gfpdf_field_middleware', [ $this->model, 'field_middle_blacklist' ], 10, 7 ); - /* Tap into GF notifications */ - add_filter( - 'gform_notification', - [ - $this->model, - 'notifications', - ], - 9999, - 3 - ); /* ensure Gravity PDF is one of the last filters to be applied */ + /* Gravity Forms PDF Attachments */ + add_filter( 'gform_notification', [ $this->model, 'notifications' ], 9999, 3 ); /* Change mPDF settings */ add_filter( 'mpdf_font_data', [ $this->model, 'register_custom_font_data_with_mPDF' ] ); @@ -205,10 +186,16 @@ public function add_filters() { add_filter( 'gfpdf_mpdf_init_class', [ $this->model, 'set_watermark_font' ], 10, 4 ); /* Process mergetags and shortcodes in PDF */ + add_filter( 'gfpdf_pdf_core_template_html_output', [ $this->gform, 'process_tags' ], 10, 3 ); add_filter( 'gfpdf_pdf_html_output', [ $this->gform, 'process_tags' ], 10, 3 ); add_filter( 'gfpdf_pdf_html_output', 'do_shortcode' ); - add_filter( 'gfpdf_pdf_core_template_html_output', [ $this->gform, 'process_tags' ], 10, 3 ); + /* Add support for ?html=1 helper parameter */ + $add_view_html_debugger = function( $html, $form, $entry, $pdf_settings, $helper_pdf ) { + return $this->add_view_html_debugger( $html, $form, $entry, $pdf_settings, $helper_pdf ); + }; + + add_filter( 'gfpdf_pdf_html_output', $add_view_html_debugger, 9999, 5 ); /* Backwards compatibility for our Tier 2 plugin */ add_filter( 'gfpdfe_pre_load_template', [ 'PDFRender', 'prepare_ids' ], 1, 8 ); @@ -217,33 +204,42 @@ public function add_filters() { add_filter( 'gfpdf_template_args', [ $this->model, 'preprocess_template_arguments' ] ); add_filter( 'gfpdf_pdf_html_output', [ $this->view, 'autoprocess_core_template_options' ], 5, 4 ); - /* Cleanup filters */ - add_filter( 'gform_before_resend_notifications', [ $this->model, 'resend_notification_pdf_cleanup' ], 10, 2 ); - - /* Third Party Conflict Fixes */ - add_filter( 'gfpdf_pre_view_or_download_pdf', [ $this, 'sgoptimizer_html_minification_fix' ] ); - add_filter( 'gfpdf_legacy_pre_view_or_download_pdf', [ $this, 'sgoptimizer_html_minification_fix' ] ); - add_filter( - 'gfpdf_pre_pdf_generation_output', - function() { - add_filter( 'weglot_active_translation', '__return_false' ); - } - ); - /* Meta boxes */ add_filter( 'gform_entry_detail_meta_boxes', [ $this->model, 'register_pdf_meta_box' ], 10, 3 ); - /* Page field support */ - add_filter( 'gfpdf_current_form_object', [ $this->model, 'register_page_fields' ] ); + /* Manipulate the form object (array) when generating PDFs */ + $add_current_form_object_hooks = function( $form, $entry, $source ) { + return $this->add_current_form_object_hooks( $form, $entry, $source ); + }; + + add_filter( 'gfpdf_current_form_object', $add_current_form_object_hooks, 10, 3 ); + + /* Manipulate the PDF settings object (array) when generating PDFs */ + $add_current_pdf_settings_object_hooks = function( $pdf_settings, $form, $entry ) { + return $this->add_current_pdf_settings_object_hooks( $pdf_settings, $form, $entry ); + }; + + add_filter( 'gfpdf_current_pdf_settings_object', $add_current_pdf_settings_object_hooks, 10, 3 ); } /** - * Determines if we should process the PDF at this stage - * Fires just before the main WP_Query is executed (we don't need it) + * Processes the View/Download PDF Endpoint + * + * Endpoint URLs: + * + * example format -> https://example.com/pdf/{pdfId}/{entryId}/ + * + * view -> https://example.com/pdf/66307560bcdf4/2403/ + * download -> https://example.com/pdf/66307560bcdf4/2403/download/ + * add print dialog -> https://example.com/pdf/66307560bcdf4/2403/?print=1 + * + * Recommend you generate the URL with a shortcode or merge tag + * See https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags + * + * This method runs just before the main WP_Query class is executed * * @return void * @since 4.0 - * */ public function process_pdf_endpoint() { @@ -252,8 +248,6 @@ public function process_pdf_endpoint() { return null; } - $this->prevent_index(); - $pid = $GLOBALS['wp']->query_vars['pid']; $lid = (int) $GLOBALS['wp']->query_vars['lid']; $action = ( ( isset( $GLOBALS['wp']->query_vars['action'] ) ) && $GLOBALS['wp']->query_vars['action'] === 'download' ) ? 'download' : 'view'; @@ -283,7 +277,7 @@ public function process_pdf_endpoint() { * * @return void * @since 4.0 - * + * @deprecated 4.0 Added for backwards compatibility with v3 PDF links, but ideally should not be used */ public function process_legacy_pdf_endpoint() { @@ -292,7 +286,7 @@ public function process_legacy_pdf_endpoint() { return null; } - $this->prevent_index(); + _doing_it_wrong( __METHOD__, 'Legacy PDF URLs are deprecated. Replace with the [gravitypdf] shortcode or PDF merge tags. See https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags for usage instructions.', '4.0' ); $config = [ 'lid' => (int) explode( ',', $_GET['lid'] )[0], @@ -303,13 +297,6 @@ public function process_legacy_pdf_endpoint() { ]; /* phpcs:enable */ - $this->log->notice( - 'Processing Legacy PDF endpoint.', - [ - 'config' => $config, - ] - ); - /* Attempt to find a valid config */ $pid = $this->model->get_legacy_config( $config ); @@ -322,6 +309,16 @@ public function process_legacy_pdf_endpoint() { $GLOBALS['wp']->query_vars['pid'] = $pid; $GLOBALS['wp']->query_vars['lid'] = $config['lid']; + $this->log->notice( + 'Processing Legacy PDF endpoint.', + [ + 'config' => $config, + 'pid' => $pid, + ] + ); + + $this->log->warning( 'Legacy PDF URLs are deprecated. Replace with the [gravitypdf] shortcode or PDF merge tags. See https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags for usage instructions.' ); + /* Send to our model to handle validation / authentication */ do_action( 'gfpdf_legacy_pre_view_or_download_pdf', $config['lid'], $pid, $config['action'] ); $results = $this->model->process_pdf( $pid, $config['lid'], $config['action'] ); @@ -338,6 +335,13 @@ public function process_legacy_pdf_endpoint() { public function add_pre_pdf_hooks() { add_filter( 'wp_kses_allowed_html', [ $this->view, 'allow_pdf_html' ] ); add_filter( 'safe_style_css', [ $this->view, 'allow_pdf_css' ] ); + + $this->misc->maybe_load_gf_entry_detail_class(); /* Backwards compatible for legacy templates */ + + /* Gravity Wiz Populate Anything support */ + if ( function_exists( 'gp_populate_anything' ) ) { + $this->model->enable_gp_populate_anything(); + } } /** @@ -346,16 +350,50 @@ public function add_pre_pdf_hooks() { public function remove_pre_pdf_hooks() { remove_filter( 'wp_kses_allowed_html', [ $this->view, 'allow_pdf_html' ] ); remove_filter( 'safe_style_css', [ $this->view, 'allow_pdf_css' ] ); + + /* Gravity Wiz Populate Anything support */ + if ( function_exists( 'gp_populate_anything' ) ) { + $this->model->disable_gp_populate_anything(); + } } /** - * Prevent the PDF Endpoints being indexed + * Actions / hooks to run prior to streaming PDF to the browser + * These hooks will not be run when sending notifications, using GPDFAPI::create_pdf(), * - * @since 5.2 + * @return void + * + * @since 6.12 */ - public function prevent_index() { - if ( ! headers_sent() ) { - header( 'X-Robots-Tag: noindex, nofollow', true ); + protected function add_pre_view_or_download_pdf_hooks( $form, $entry, $settings ) { + $this->prevent_index(); + + /* + * Stop Weglot trying to transform the binary PDF + * See https://github.com/GravityPDF/gravity-pdf/pull/1505 + */ + add_filter( 'weglot_active_translation', '__return_false' ); + + /* + * Stop WP External Links plugin trying to transform the binary PDF + * See https://github.com/GravityPDF/gravity-pdf/issues/386 + */ + add_filter( 'wpel_apply_settings', '__return_false' ); + + /* + * Support ?data=1 helper parameter + * See https://docs.gravitypdf.com/v6/developers/helper-parameters#data1 + */ + if ( $this->view->maybe_view_form_data() ) { + $this->view->view_form_data( \GPDFAPI::get_form_data( $entry['id'] ) ); + } + + /* + * Support ?html=1 helper parameter + * See https://docs.gravitypdf.com/v6/developers/helper-parameters#html1 + */ + if ( rgget( 'html' ) && Debug::is_enabled_and_can_view() ) { + add_filter( 'gfpdf_override_pdf_bypass', '__return_true' ); } } @@ -363,30 +401,119 @@ public function prevent_index() { * Disables the Siteground HTML Minifier when generating PDFs for the browser * * @since 5.1.5 - * - * @see https://github.com/GravityPDF/gravity-pdf/issues/863 + * @see https://github.com/GravityPDF/gravity-pdf/issues/863 + * @deprecated 6.12 All buffers are auto-closed before a PDF is sent to the browser */ public function sgoptimizer_html_minification_fix() { - if ( class_exists( '\SiteGround_Optimizer\Minifier\Minifier' ) ) { + _doing_it_wrong( __METHOD__, 'This method has been removed and no alternative is available.', '6.12' ); + } + + /** + * Modify the form object specifically for the PDF request + * + * @param array $form + * @param array $entry + * @param string $source + * + * @return array + * + * @since 6.12 + */ + protected function add_current_form_object_hooks( $form, $entry, $source ) { + /* Make Page fields first class citizens in the form object */ + $form = $this->model->register_page_fields( $form ); + + /* Gravity Perks Conditional Logic Date Fields support */ + if ( method_exists( 'GWConditionalLogicDateFields', 'convert_conditional_logic_date_field_values' ) ) { + $form = \GWConditionalLogicDateFields::convert_conditional_logic_date_field_values( $form ); + } + + /* Gravity Perks Populate Anything support */ + if ( function_exists( 'gp_populate_anything' ) ) { + $form = $this->model->gp_populate_anything_hydrate_form( $form, $entry ); + } + + return $form; + } + + /** + * Modify the PDF settings specifically for the PDF request + * + * @param array $pdf_settings + * @param array $form + * @param array $entry + * + * @return array + * + * @since 6.12 + */ + protected function add_current_pdf_settings_object_hooks( $pdf_settings, $form, $entry ) { + $pdf_settings = $this->model->apply_backwards_compatibility_filters( $pdf_settings, $entry ); + + return $pdf_settings; + } + + /** + * A debugging tool that will output the HTML mark-up for the PDF to the browser + * + * Use ?html=1 to active when the website is in development/staging mode and the current logged-in + * user has appropriate capabilities + * + * @param string $html + * @param array $form + * @param array $entry + * @param array $pdf_settings + * @param Helper_PDF $helper_pdf + * + * @return string + * + * @since 6.12 + * + * @internal was originally included in \GFPDF\Helper\Helper_PDF::maybe_display_raw_html() + * @link https://docs.gravitypdf.com/v6/developers/helper-parameters#html1 + */ + protected function add_view_html_debugger( $html, $form, $entry, $pdf_settings, $helper_pdf ) { + if ( ! is_string( $html ) ) { + return $html; + } + + if ( ! rgget( 'html' ) ) { + return $html; + } + + if ( ! Debug::is_enabled_and_can_view() ) { + return $html; + } + + /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ + echo apply_filters( 'gfpdf_pre_html_browser_output', $html, $pdf_settings, $entry, $form, $helper_pdf ); + exit; + } - /* Remove the shutdown buffer and manually close an open buffers */ - $minifier = Minifier::get_instance(); - remove_action( 'shutdown', [ $minifier, 'end_html_minifier_buffer' ] ); + /** + * Try to prevent the PDF being indexed or cached by the web server + * + * @since 5.2 + * @since 6.12 Set DONOTCACHEPAGE constant (brought forward in the request cycle) + */ + public function prevent_index() { + if ( ! headers_sent() ) { + header( 'X-Robots-Tag: noindex, nofollow', true ); + } - while ( ob_get_level() > 0 ) { - ob_end_clean(); - } + if ( ! defined( 'DONOTCACHEPAGE' ) ) { + define( 'DONOTCACHEPAGE', true ); } } /** - * Output PDF error to user + * Display appropriate error to user when PDF cannot be display * - * @param Object $error The WP_Error object + * @param \WP_Error $error The WP_Error object * * @since 4.0 */ - private function pdf_error( $error ) { + protected function pdf_error( $error ) { $this->log->error( 'PDF Generation Error.', @@ -421,7 +548,7 @@ private function pdf_error( $error ) { if ( $this->gform->has_capability( 'gravityforms_view_settings' ) || in_array( $error->get_error_code(), $whitelist_errors, true ) ) { wp_die( esc_html( $error->get_error_message() ), $status_code ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { - wp_die( esc_html__( 'There was a problem generating your PDF', 'gravity-forms-pdf-extended' ), $status_code ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + wp_die( esc_html__( 'There was a problem creating the PDF', 'gravity-forms-pdf-extended' ), $status_code ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } } diff --git a/src/Controller/Controller_Pdf_Queue.php b/src/Controller/Controller_Pdf_Queue.php index cadd993b1..fa2de1f83 100644 --- a/src/Controller/Controller_Pdf_Queue.php +++ b/src/Controller/Controller_Pdf_Queue.php @@ -210,11 +210,6 @@ public function do_we_disable_notification( $default, $notification, $form, $ent */ public function queue_async_form_submission_tasks( $entry, $form ) { $this->queue_async_tasks( $form, $entry ); - - if ( count( $this->queue->get_data() ) > 0 ) { - $this->queue_cleanup_task( $form, $entry ); - } - $this->dispatch_queue(); } @@ -255,17 +250,10 @@ public function queue_async_tasks( $form, $entry ) { * @return void * * @since 6.11.0 + * @deprecated 6.12.0 Caching layer + auto-purge added */ public function queue_cleanup_task( $form, $entry ) { - $this->queue->push_to_queue( - [ - [ - 'id' => sprintf( 'cleanup-pdf-%d-%d', $form['id'], $entry['id'] ), - 'func' => '\GFPDF\Statics\Queue_Callbacks::cleanup_pdfs', - 'args' => [ $form['id'], $entry['id'] ], - ], - ] - ); + _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); } /** @@ -477,6 +465,6 @@ public function reset_queue() { * @deprecated 6.11 */ public function queue_async_resend_notification_tasks( $notification, $form, $entry ) { - _doing_it_wrong( esc_html( 'queue_async_resend_notification_tasks() was removed in Gravity PDF 6.11' ) ); + _doing_it_wrong( __METHOD__, 'This method has been removed and no alternative is available.', '6.11' ); } } diff --git a/src/Controller/Controller_Upgrade_Routines.php b/src/Controller/Controller_Upgrade_Routines.php index 0d5db43ae..a34f67a2e 100644 --- a/src/Controller/Controller_Upgrade_Routines.php +++ b/src/Controller/Controller_Upgrade_Routines.php @@ -54,6 +54,11 @@ public function maybe_run_upgrade( string $old_version, string $current_version $this->update_background_processing_values(); $this->upgrade_custom_fonts(); } + + /* Remove scheduled event(s) so the event can be reregistered with a new frequency */ + if ( version_compare( $current_version, '6.12.0', '>=' ) && version_compare( $old_version, '6.12.0', '<' ) ) { + wp_clear_scheduled_hook( 'gfpdf_cleanup_tmp_dir' ); + } } /** diff --git a/src/Helper/Helper_Misc.php b/src/Helper/Helper_Misc.php index 14296161f..04aa5f10e 100644 --- a/src/Helper/Helper_Misc.php +++ b/src/Helper/Helper_Misc.php @@ -721,12 +721,11 @@ public function get_legacy_ids( $entry_id, $settings ) { * @return void * * @since 4.0 + * + * @deprecated 6.12 compatibility code no longer required */ public function maybe_add_multicurrency_support() { - if ( class_exists( 'GFMultiCurrency' ) && method_exists( 'GFMultiCurrency', 'admin_pre_render' ) ) { - $currency = GFMultiCurrency::init(); - add_filter( 'gform_form_post_get_meta', [ $currency, 'admin_pre_render' ] ); - } + _doing_it_wrong( __METHOD__, 'This method has been removed and no alternative is available.', '6.12' ); } /** diff --git a/src/Helper/Helper_PDF.php b/src/Helper/Helper_PDF.php index ca842f365..12b46f026 100644 --- a/src/Helper/Helper_PDF.php +++ b/src/Helper/Helper_PDF.php @@ -3,6 +3,7 @@ namespace GFPDF\Helper; use Exception; +use GFPDF\Statics\Cache; use GFPDF_Vendor\Mpdf\Config\FontVariables; use GFPDF_Vendor\Mpdf\Mpdf; use GFPDF_Vendor\Mpdf\MpdfException; @@ -178,7 +179,6 @@ class Helper_PDF { * * @param array $entry The Gravity Form Entry to be processed * @param array $settings The Gravity PDF Settings Array - * * @param Helper_Abstract_Form $gform * @param Helper_Data $data * @param Helper_Misc $misc @@ -200,6 +200,7 @@ public function __construct( $entry, $settings, Helper_Abstract_Form $gform, Hel $this->form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, 'initialize_pdf_class' ); $this->set_path(); + $this->set_print_dialog( ! empty( $settings['print'] ) ); } /** @@ -270,9 +271,6 @@ public function render_html( $args = [], $html = '' ) { $html = apply_filters( 'gfpdf_pdf_html_output', $html, $form, $this->entry, $args['settings'], $this ); $html = apply_filters( 'gfpdf_pdf_html_output_' . $form['id'], $html, $this->gform, $this->entry, $args['settings'], $this ); - /* Check if we should output the HTML to the browser, for debugging */ - $this->maybe_display_raw_html( $html ); - /* Write the HTML to mPDF */ $this->mpdf->WriteHTML( $html ); } @@ -284,6 +282,7 @@ public function render_html( $args = [], $html = '' ) { * * @throws MpdfException * @since 4.0 + * @since 6.12 All PDF requests have been standardized to use the functions/methods in \GPDFAPI::create_pdf(), and the DISPLAY/DOWNLOAD options are no longer used by core */ public function generate() { @@ -519,7 +518,7 @@ public function set_JS( $js ) { /** * - * Get the current Gravity Form Entry + * Get the current Gravity Forms Entry * * @return array * @since 4.0 @@ -539,6 +538,16 @@ public function get_settings() { return $this->settings; } + /** + * Get the current Gravity Forms form object + * + * @return array + * @since 6.12 + */ + public function get_form() { + return $this->form; + } + /** * Get the current PDF Name * @@ -584,16 +593,10 @@ public function get_path() { public function set_path( $path = '' ) { if ( empty( $path ) ) { - /* build our PDF path location */ - $path = $this->data->template_tmp_location . $this->entry['form_id'] . $this->entry['id'] . $this->settings['id'] . '/'; - } else { - /* ensure the path ends with a forward slash */ - if ( substr( $path, -1 ) !== '/' ) { - $path .= '/'; - } + $path = Cache::get_path( $this->form, $this->entry, $this->settings ); } - $this->path = $path; + $this->path = trailingslashit( $path ); } /** @@ -862,45 +865,6 @@ protected function load_html( $args = [] ) { return ob_get_clean(); } - - /** - * Allow site admins to view the RAW HTML if needed - * - * @param string $html The HTML that should be output to the browser - * - * @return void - * - * @since 4.0 - */ - protected function maybe_display_raw_html( $html ) { - - $options = \GPDFAPI::get_options_class(); - - /* Disregard if PDF is being saved */ - if ( $this->output === 'SAVE' ) { - return; - } - - /* Disregard if `?html` URL parameter doesn't exist */ - if ( ! rgget( 'html' ) ) { - return; - } - - /* Disregard if PDF Debug Mode off AND the environment is production */ - if ( $options->get_option( 'debug_mode', 'No' ) === 'No' && ( ! function_exists( 'wp_get_environment_type' ) || wp_get_environment_type() === 'production' ) ) { - return; - } - - /* Check if user has permission to view info */ - if ( ! $this->gform->has_capability( 'gravityforms_edit_forms' ) ) { - return; - } - - /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ - echo apply_filters( 'gfpdf_pre_html_browser_output', $html, $this->settings, $this->entry, $this->gform, $this ); - exit; - } - /** * Prompt the print dialog box * @@ -914,17 +878,6 @@ protected function show_print_dialog() { } } - /** - * Sets the image DPI in the PDF - * - * @return void - * - * @since 4.0 - */ - protected function set_image_dpi() { - _doing_it_wrong( __METHOD__, esc_html__( 'This method has been removed because mPDF no longer supports setting the image DPI after the class is initialised.', 'gravity-forms-pdf-extended' ), '5.2' ); - } - /** * Sets the text direction in the PDF (RTL support) * diff --git a/src/Model/Model_PDF.php b/src/Model/Model_PDF.php index 89fd0c3a0..3c07e75ef 100644 --- a/src/Model/Model_PDF.php +++ b/src/Model/Model_PDF.php @@ -6,6 +6,7 @@ use GF_Field; use GFCommon; use GFFormsModel; +use GFPDF\Controller\Controller_PDF; use GFPDF\Helper\Fields\Field_Default; use GFPDF\Helper\Fields\Field_Products; use GFPDF\Helper\Helper_Abstract_Field_Products; @@ -49,6 +50,8 @@ * Handles all the PDF display logic * * @since 4.0 + * + * @method Controller_PDF getController */ class Model_PDF extends Helper_Abstract_Model { @@ -155,7 +158,7 @@ public function __construct( Helper_Abstract_Form $gform, LoggerInterface $log, } /** - * Our Middleware used to handle the authentication process + * Authentication request then generate and display PDF * * @param string $pid The Gravity Form PDF Settings ID * @param integer $lid The Gravity Form Entry ID @@ -163,34 +166,30 @@ public function __construct( Helper_Abstract_Form $gform, LoggerInterface $log, * * @return WP_Error * @since 4.0 - * + * @since 6.12 View/Download PDF creation workflow standardized with Save PDF workflow */ public function process_pdf( $pid, $lid, $action = 'view' ) { - /** - * Check if we have a valid Gravity Form Entry and PDF Settings ID - */ + /* Get entry */ $entry = $this->gform->get_entry( $lid ); - - /* not a valid entry */ if ( is_wp_error( $entry ) ) { $this->log->error( - 'Invalid Entry.', + 'Invalid Entry', [ - 'entry' => $entry, + 'entry_id' => $lid, + 'WP_Error_Message' => $entry->get_error_message(), + 'WP_Error_Code' => $entry->get_error_code(), ] ); return $entry; /* return error */ } + /* Get PDF setting */ $settings = $this->options->get_pdf( $entry['form_id'], $pid ); - - /* Not valid settings */ if ( is_wp_error( $settings ) ) { - $this->log->error( - 'Invalid PDF Settings.', + 'Invalid PDF Settings', [ 'entry' => $entry, 'WP_Error_Message' => $settings->get_error_message(), @@ -201,23 +200,33 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { return $settings; /* return error */ } - /* Add our download setting */ + /* + * Prior to 6.12 this action was saved to the PDF settings, passed to Helper_PDF, and used to + * stream the document to the client correctly. Since 6.12, we no longer need to pass this value + * to the underlying PDF generator. For backwards compatibility we've included this in case any + * user-land code makes use of it in their custom middleware. + */ $settings['pdf_action'] = $action; - /** - * Our middleware authenticator - * Allow users to tap into our middleware and add or remove additional authentication layers + /* + * Authenticate the request to prevent unauthorized access to the PDF + * + * Default middleware filters include: + * - middle_public_access + * - middle_signed_url_access + * - middle_active + * - middle_conditional + * - middle_owner_restriction + * - middle_logged_out_timeout + * - middle_auth_logged_out_user + * - middle_user_capability * - * Default middleware includes 'middle_public_access', 'middle_active', 'middle_conditional', 'middle_owner_restriction', 'middle_logged_out_timeout', 'middle_auth_logged_out_user', 'middle_user_capability' - * If WP_Error is returned the PDF won't be parsed + * If any of the filters return a WP_Error object the request will not be fulfilled * - * See https://docs.gravitypdf.com/v6/developers/filters/gfpdf_pdf_middleware/ for more details about this filter + * Refer to https://docs.gravitypdf.com/v6/developers/filters/gfpdf_pdf_middleware/ */ $middleware = apply_filters( 'gfpdf_pdf_middleware', false, $entry, $settings ); - - /* Throw error */ if ( is_wp_error( $middleware ) ) { - $this->log->error( 'PDF Authentication Failure.', [ @@ -231,17 +240,101 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { return $middleware; } - /* Add backwards compatibility support for certain settings */ - $settings = $this->apply_backwards_compatibility_filters( $settings, $entry ); + /* + * Normalize the PDF action + * The PDF cache introduced in 6.12 relies on a hash generated from the form, entry, and pdf settings + * To prevent cache misses we need to ensure we don't unnecessarily modify the settings array + */ + unset( $settings['pdf_action'] ); + $action = apply_filters( 'gfpdfe_pdf_output_type', $action ); /* Backwards compat */ + $action = in_array( $action, [ 'view', 'download' ], true ) ? $action : 'view'; - /* Ensure Gravity Forms dependency loaded */ - $this->misc->maybe_load_gf_entry_detail_class(); + /* Get the PDF document for the request */ + $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); - /* If we are here we can generate our PDF */ - $controller = $this->getController(); - $controller->view->generate_pdf( $entry, $settings ); + do_action( 'gfpdf_view_or_download_pdf', $form, $entry, $settings ); + + /* + * Support the print dialog option + * The PDF document embeds this preference directly in the source code so we need to + * force the cache to be bypassed. + */ + if ( rgget( 'print' ) === '1' ) { + $settings['print'] = true; + } + + $path_to_pdf = $this->generate_and_save_pdf( $entry, $settings ); + + /* Send error upstream for logging and output */ + if ( is_wp_error( $path_to_pdf ) ) { + return $path_to_pdf; + } + + /* Verify the PDF can be sent to the client */ + if ( headers_sent( $filename, $linenumber ) ) { + $this->log->error( + 'Server headers already sent', + [ + 'filename' => $filename, + 'linenumber' => $linenumber, + ] + ); + + return new WP_Error( 'headers_sent', __( 'The PDF cannot be displayed because the server headers have already been sent.', 'gravity-forms-pdf-extended' ) ); + } + + /* Force any active buffers to close and delete its content */ + while ( ob_get_level() > 0 ) { + ob_end_clean(); + } + + do_action( 'gfpdf_post_view_or_download_pdf', $path_to_pdf, $form, $entry, $settings ); + + /* Send the PDF to the client */ + header( 'Content-Type: application/pdf' ); + + /* + * Set the filename, supporting the new utf-8 syntax + backwards compatibility + * Refer to RFC 8187 https://www.rfc-editor.org/rfc/rfc8187.html + */ + header( + sprintf( + 'Content-disposition: %1$s; filename="%2$s"; filename*=utf-8\'\'%2$s', + $action === 'view' ? 'inline' : 'attachment', + rawurlencode( basename( $path_to_pdf ) ), + ) + ); - return null; + /* only add the length if the server is not using compression */ + if ( empty( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) { + header( sprintf( 'Content-Length: %d', filesize( $path_to_pdf ) ) ); + } + + /* Tell client to download the file */ + if ( $action === 'download' ) { + header( 'Content-Description: File Transfer' ); + header( 'Content-Transfer-Encoding: binary' ); + } + + /* Set appropriate headers for local browser caching */ + $last_modified_time = filemtime( $path_to_pdf ); + $etag = md5( $path_to_pdf ); /* the file path includes a unique hash that automatically changes when a PDF does */ + + header( sprintf( 'Last-Modified: %s GMT', gmdate( 'D, d M Y H:i:s', $last_modified_time ) ) ); + header( sprintf( 'Etag: %s', $etag ) ); + header( 'Cache-Control: no-cache, private' ); + header( 'Pragma: no-cache' ); + header( 'Expires: 0' ); + + /* Tell client they can display the PDF from the local cache if it is still current */ + if ( ! empty( $_SERVER['HTTP_IF_NONE_MATCH'] ) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag ) { + header( 'HTTP/1.1 304 Not Modified' ); + exit; + } + + readfile( $path_to_pdf ); /* phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile */ + + exit; } /** @@ -1143,139 +1236,134 @@ public function maybe_attach_to_notification( $notification, $settings, $entry = /** * Generate and save the PDF to disk * - * @param array $entry The Gravity Form entry array (usually passed in as a filter or pulled using GFAPI::get_entry( $id ) ) - * @param array $settings The PDF configuration settings for the particular entry / form being processed + * @param array $entry The Gravity Forms entry (from \GFAPI::get_entry) + * @param array $pdf_settings The Gravity PDF settings (from GPDFAPI::get_pdf()) * - * @return string|WP_Error Return the full path to the PDF, or a WP_Error on failure + * @return string|WP_Error Return the full path to the PDF, or a WP_Error on failure * - * @throws Exception * @since 4.0 + * @since 6.12 The view/download endpoints route through this method */ - public function generate_and_save_pdf( $entry, $settings ) { + public function generate_and_save_pdf( $entry, $pdf_settings ) { + + $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); + $entry = apply_filters( 'gfpdf_current_entry_object', $entry, $form, $pdf_settings, __FUNCTION__ ); + $pdf_settings = apply_filters( 'gfpdf_current_pdf_settings_object', $pdf_settings, $form, $entry, __FUNCTION__ ); + $filename = $this->get_pdf_name( $pdf_settings, $entry ); - $pdf_generator = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates, $this->log ); - $pdf_generator->set_filename( $this->get_pdf_name( $settings, $entry ) ); + do_action( 'gfpdf_pre_generate_and_save_pdf', $form, $entry, $pdf_settings ); + + $pdf_generator = new Helper_PDF( $entry, $pdf_settings, $this->gform, $this->data, $this->misc, $this->templates, $this->log ); + $pdf_generator->set_filename( $filename ); $pdf_generator = apply_filters( 'gfpdf_pdf_generator_pre_processing', $pdf_generator ); - if ( $this->process_and_save_pdf( $pdf_generator ) ) { - $pdf_path = $pdf_generator->get_full_pdf_path(); - if ( is_file( $pdf_path ) ) { - return $pdf_path; - } + if ( ! $this->process_and_save_pdf( $pdf_generator ) ) { + return new WP_Error( 'pdf_generation_failure', esc_html__( 'There was a problem creating the PDF', 'gravity-forms-pdf-extended' ) ); } - return new WP_Error( 'pdf_generation_failure', esc_html__( 'The PDF could not be saved.', 'gravity-forms-pdf-extended' ) ); + do_action( 'gfpdf_post_generate_and_save_pdf', $form, $entry, $pdf_settings ); + return $pdf_generator->get_full_pdf_path(); } /** * Generate and save PDF to disk * - * @param Helper_PDF $pdf The Helper_PDF object + * @param Helper_PDF $pdf_generator The Helper_PDF object * - * @return boolean + * @return bool * - * @throws Exception * @since 4.0 */ - public function process_and_save_pdf( Helper_PDF $pdf ) { + public function process_and_save_pdf( Helper_PDF $pdf_generator ) { /** * See https://docs.gravitypdf.com/v6/developers/filters/gfpdf_override_pdf_bypass/ for usage * * @since 4.2 */ - $pdf_override = apply_filters( 'gfpdf_override_pdf_bypass', false, $pdf ); + $pdf_override = apply_filters( 'gfpdf_override_pdf_bypass', false, $pdf_generator ); - /* Check that the PDF hasn't already been created this session */ - if ( $pdf_override || ! $this->does_pdf_exist( $pdf ) ) { - - /* Ensure Gravity Forms dependency loaded */ - $this->misc->maybe_load_gf_entry_detail_class(); + /* If cached PDF already exists then return early */ + if ( ! $pdf_override && $this->does_pdf_exist( $pdf_generator ) ) { + return true; + } - /* Enable Multicurrency support */ - $this->misc->maybe_add_multicurrency_support(); + /* Get required parameters */ + $entry = $pdf_generator->get_entry(); + $settings = $pdf_generator->get_settings(); + $form = $pdf_generator->get_form(); - /* Get required parameters */ - $entry = $pdf->get_entry(); - $settings = $pdf->get_settings(); - $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); + do_action( 'gfpdf_pre_pdf_generation', $form, $entry, $settings, $pdf_generator ); - do_action( 'gfpdf_pre_pdf_generation', $form, $entry, $settings, $pdf ); - - /** - * Load our arguments that should be accessed by our PDF template - * - * @var array - */ - $args = $this->templates->get_template_arguments( - $form, - $this->misc->get_fields_sorted_by_id( $form['id'] ), - $entry, - $this->get_form_data( $entry ), - $settings, - $this->templates->get_config_class( $settings['template'] ), - $this->misc->get_legacy_ids( $entry['id'], $settings ) - ); + /* + * Load our arguments that should be accessed by our PDF template + */ + $args = $this->templates->get_template_arguments( + $form, + $this->misc->get_fields_sorted_by_id( $form['id'] ), + $entry, + $this->get_form_data( $entry ), + $settings, + $this->templates->get_config_class( $settings['template'] ), + $this->misc->get_legacy_ids( $entry['id'], $settings ) + ); - /* Add backwards compatibility support */ - $GLOBALS['wp']->query_vars['pid'] = $settings['id']; - $GLOBALS['wp']->query_vars['lid'] = $entry['id']; + /* Add backwards compatibility support */ + $GLOBALS['wp']->query_vars['pid'] = $settings['id']; + $GLOBALS['wp']->query_vars['lid'] = $entry['id']; - try { + try { - /* Initialise our PDF helper class */ - $pdf->init(); - $pdf->set_template(); - $pdf->set_output_type( 'save' ); + /* Initialise our PDF helper class */ + $pdf_generator->init(); + $pdf_generator->set_template(); + $pdf_generator->set_output_type( 'save' ); - /* Add Backwards compatibility support for our v3 Tier 2 Add-on */ - if ( isset( $settings['advanced_template'] ) && strtolower( $settings['advanced_template'] ) === 'yes' ) { + /* Add Backwards compatibility support for our v3 Tier 2 Add-on */ + if ( isset( $settings['advanced_template'] ) && strtolower( $settings['advanced_template'] ) === 'yes' ) { - /* Check if we should process this document using our legacy system */ - if ( $this->handle_legacy_tier_2_processing( $pdf, $entry, $settings, $args ) ) { - return true; - } + /* Check if we should process this document using our legacy system */ + if ( $this->handle_legacy_tier_2_processing( $pdf_generator, $entry, $settings, $args ) ) { + return true; } + } - /* Render the PDF template HTML */ - $pdf->render_html( $args ); + /* Render the PDF template HTML */ + $pdf_generator->render_html( $args ); - /* Generate and save the PDF */ - $pdf->save_pdf( $pdf->generate() ); + /* Generate and save the PDF */ + $pdf_generator->save_pdf( $pdf_generator->generate() ); - do_action( 'gfpdf_post_pdf_generation', $form, $entry, $settings, $pdf ); + do_action( 'gfpdf_post_pdf_generation', $form, $entry, $settings, $pdf_generator ); - return true; - } catch ( Exception $e ) { + return true; + } catch ( Exception $e ) { - $this->log->error( - 'PDF Generation Error', - [ - 'pdf' => $pdf, - 'exception' => $e->getMessage(), - ] - ); + $this->log->error( + 'PDF Generation Error', + [ + 'pdf' => $pdf_generator, + 'exception' => $e->getMessage(), + ] + ); - return false; - } + return false; } - - return true; } /** * Check if the current PDF to be processed already exists on disk * - * @param Helper_PDF $pdf The Helper_PDF Object + * @param Helper_PDF $pdf_generator The Helper_PDF Object * * @return boolean * * @since 4.0 */ - public function does_pdf_exist( Helper_PDF $pdf ) { + public function does_pdf_exist( Helper_PDF $pdf_generator ) { - if ( is_file( $pdf->get_full_pdf_path() ) ) { + if ( is_file( $pdf_generator->get_full_pdf_path() ) ) { return true; } @@ -1408,16 +1496,16 @@ public function get_form_data( $entry ) { /** * Handles the loading and running of our legacy Tier 2 PDF templates * - * @param Helper_PDF $pdf The Helper_PDF object - * @param array $entry The Gravity Forms raw entry data - * @param array $settings The Gravity PDF settings - * @param array $args The data that should be passed directly to a PDF template + * @param Helper_PDF $pdf_generator The Helper_PDF object + * @param array $entry The Gravity Forms raw entry data + * @param array $settings The Gravity PDF settings + * @param array $args The data that should be passed directly to a PDF template * * @return bool * * @since 4.0 */ - public function handle_legacy_tier_2_processing( Helper_PDF $pdf, $entry, $settings, $args ) { + public function handle_legacy_tier_2_processing( Helper_PDF $pdf_generator, $entry, $settings, $args ) { $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); @@ -1425,10 +1513,10 @@ public function handle_legacy_tier_2_processing( Helper_PDF $pdf, $entry, $setti 'gfpdfe_pre_load_template', $form['id'], $entry['id'], - basename( $pdf->get_template_path() ), + basename( $pdf_generator->get_template_path() ), $form['id'] . $entry['id'], - $this->misc->backwards_compat_output( $pdf->get_output_type() ), - $pdf->get_filename(), + $this->misc->backwards_compat_output( $pdf_generator->get_output_type() ), + $pdf_generator->get_filename(), $this->misc->backwards_compat_conversion( $settings, $form, $entry ), $args ); /* Backwards Compatibility */ @@ -1954,35 +2042,36 @@ public function trigger_post_save_pdf( $form, $entry, $settings, $pdf ) { * @since 4.0 */ public function cleanup_tmp_dir() { - $max_file_age = time() - 12 * 3600; /* Max age is 12 hours old */ + $max_file_age = time() - 3600; /* Max age is 1 hour old */ $tmp_directory = $this->data->template_tmp_location; - if ( is_dir( $tmp_directory ) ) { + if ( ! is_dir( $tmp_directory ) ) { + return; + } - try { - $directory_list = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( $tmp_directory, RecursiveDirectoryIterator::SKIP_DOTS ), - RecursiveIteratorIterator::CHILD_FIRST - ); + try { + $directory_list = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $tmp_directory, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); - foreach ( $directory_list as $file ) { - if ( in_array( $file->getFilename(), [ '.htaccess', 'index.html' ], true ) || strpos( realpath( $file->getPathname() ), realpath( $this->data->mpdf_tmp_location ) ) !== false ) { - continue; - } + foreach ( $directory_list as $file ) { + if ( in_array( $file->getFilename(), [ '.htaccess', 'index.html' ], true ) || strpos( realpath( $file->getPathname() ), realpath( $this->data->mpdf_tmp_location ) ) !== false ) { + continue; + } - if ( $file->isReadable() && $file->getMTime() < $max_file_age ) { - ( $file->isDir() ) ? $this->misc->rmdir( $file->getPathName() ) : unlink( $file->getPathName() ); - } + if ( $file->isReadable() && $file->getMTime() < $max_file_age ) { + ( $file->isDir() ) ? $this->misc->rmdir( $file->getPathName() ) : unlink( $file->getPathName() ); } - } catch ( Exception $e ) { - $this->log->error( - 'Filesystem Delete Error', - [ - 'dir' => $tmp_directory, - 'exception' => $e->getMessage(), - ] - ); } + } catch ( Exception $e ) { + $this->log->error( + 'Filesystem Delete Error', + [ + 'dir' => $tmp_directory, + 'exception' => $e->getMessage(), + ] + ); } } @@ -1991,8 +2080,12 @@ public function cleanup_tmp_dir() { * * @param array $form * @param int $entry_id + * + * @deprecated 6.12 Caching layer + auto-purge added */ public function cleanup_pdf_after_submission( $form, $entry_id ) { + _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); + /* Exit if background processing is enabled */ if ( $this->options->get_option( 'background_processing', 'No' ) === 'Yes' ) { return; @@ -2017,11 +2110,13 @@ public function cleanup_pdf_after_submission( $form, $entry_id ) { * * @return void * - * @internal In future we may give the option to cache PDFs to save on processing power + * @since 4.0 * - * @since 4.0 + * @deprecated 6.12 Caching layer + auto-purge added */ public function cleanup_pdf( $entry, $form ) { + _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); + $pdfs = $this->get_active_pdfs( $form['gfpdf_form_settings'] ?? [], $entry ); if ( count( $pdfs ) === 0 ) { @@ -2053,9 +2148,11 @@ public function cleanup_pdf( $entry, $form ) { * * @return array We tapped into a filter so we need to return the form object * @since 4.0 - * + * @deprecated 6.12 Caching layer + auto-purge added */ public function resend_notification_pdf_cleanup( $form, $entries ) { + _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); + foreach ( $entries as $entry_id ) { $entry = $this->gform->get_entry( $entry_id ); $this->cleanup_pdf( $entry, $form ); @@ -2166,8 +2263,10 @@ function( $val ) use ( &$flattened_fonts_array ) { * @return mixed * * @since 4.0 + * @deprecated 4.0 Added for backwards compatibility, but ideally should not be used */ public function get_legacy_config( $config ) { + _doing_it_wrong( __METHOD__, 'Legacy PDF URLs are deprecated. Replace with the [gravitypdf] shortcode or PDF merge tags. See https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags for usage instructions.', '4.0' ); /* Get the form settings */ $pdfs = $this->options->get_form_pdfs( $config['fid'] ); @@ -2412,6 +2511,10 @@ public function set_watermark_font( $mpdf, $form, $entry, $settings ) { * @since 5.3 */ public function process_gp_populate_anything( $text, $form, $entry ) { + if ( ! class_exists( 'GP_Populate_Anything_Live_Merge_Tags' ) ) { + return $text; + } + $gp = GP_Populate_Anything_Live_Merge_Tags::get_instance(); $this->disable_gp_populate_anything(); diff --git a/src/Model/Model_Shortcodes.php b/src/Model/Model_Shortcodes.php index af951cf8c..0c51f04da 100644 --- a/src/Model/Model_Shortcodes.php +++ b/src/Model/Model_Shortcodes.php @@ -44,10 +44,10 @@ class Model_Shortcodes extends Helper_Abstract_Pdf_Shortcode { * * @since 4.0 * - * @internal Deprecated in 5.2. Use self::process() + * @internal Deprecated in 5.2. Use Model_Shortcodes::process() */ public function gravitypdf( $attributes ) { - _doing_it_wrong( __METHOD__, esc_html__( 'This method has been superseded by self::process()', 'gravity-forms-pdf-extended' ), '5.2' ); + _doing_it_wrong( __METHOD__, 'This method has been replaced by Model_Shortcodes::process()', '5.2' ); return $this->process( $attributes ); } diff --git a/src/Statics/Cache.php b/src/Statics/Cache.php new file mode 100644 index 000000000..bb3b70232 --- /dev/null +++ b/src/Statics/Cache.php @@ -0,0 +1,103 @@ +template_tmp_location; + if ( is_multisite() ) { + $base_path .= get_current_blog_id(); + } + + self::$template_tmp_location = trailingslashit( $base_path ) . 'cache/'; + + return self::$template_tmp_location; + } + + /** + * Calculate a unique hash based on the form/entry/pdf objects + * + * @param array $form The form object + * @param array $entry The entry object + * @param array $pdf_settings The PDF object/settings + * + * @return string + * + * @internal if $form, $entry, or $pdf_settings are modified a new hash and PDF will be generated + * + * @since 6.12.0 + */ + public static function get_hash( $form, $entry, $pdf_settings ) { + /* + * Remove invalid field property which Gravity Forms can unintentionally add to fields when outputting field values. + * + * The issue was identified when generating a PDF and checking if a Section Break was empty. + * This eventually calls GF_Field::get_allowable_tags(), which accesses `$this->form_id` instead of `$this->formId`. + * As all form fields are PHP objects, the new property is added to the object when accessed and eventually gets hashed below. + * This becomes a problem when generating the same PDF repeatedly during a single request, as the cache is bypassed every call. + * Until the issue is fixed upstream, we'll force-remove the invalid property. + */ + array_map( + function( $field ) { + unset( $field['form_id'] ); + }, + $form['fields'] + ); + + return sprintf( + '%1$d-%2$d-%3$s', + $form['id'] ?? 0, + $entry['id'] ?? 0, + wp_hash( wp_json_encode( [ $form, $entry, $pdf_settings ] ) ) + ); + } +} diff --git a/src/Statics/Debug.php b/src/Statics/Debug.php new file mode 100644 index 000000000..861c8a7e4 --- /dev/null +++ b/src/Statics/Debug.php @@ -0,0 +1,64 @@ +get_option( 'debug_mode', 'No' ) === 'Yes'; + $wp_production_environment = ! function_exists( 'wp_get_environment_type' ) || wp_get_environment_type() === 'production'; + + return $pdf_debug_mode || ! $wp_production_environment; + } + + /** + * Check the logged-in user has a specific capability which can view the log info + * + * @return bool + * + * @since 6.12 + */ + public static function can_view(): bool { + $gform = \GPDFAPI::get_form_class(); + + return $gform->has_capability( 'gravityforms_logging' ); + + } + + /** + * Verify debug mode is enabled and the user can view the log info + * + * @return bool + * + * @since 6.12 + */ + public static function is_enabled_and_can_view(): bool { + return static::is_enabled() && static::can_view(); + } +} diff --git a/src/Statics/Queue_Callbacks.php b/src/Statics/Queue_Callbacks.php index eb89b1b45..dc864113b 100644 --- a/src/Statics/Queue_Callbacks.php +++ b/src/Statics/Queue_Callbacks.php @@ -108,8 +108,11 @@ public static function send_notification( $form_id, $entry_id, $notification ) { * @throws Exception * * @since 5.0 + * @deprecated 6.12 Caching layer + auto-purge added */ public static function cleanup_pdfs( $form_id, $entry_id ) { + _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); + $gform = GPDFAPI::get_form_class(); $data = GPDFAPI::get_data_class(); $misc = GPDFAPI::get_misc_class(); diff --git a/src/View/View_PDF.php b/src/View/View_PDF.php index c3f20c963..cc99ba823 100644 --- a/src/View/View_PDF.php +++ b/src/View/View_PDF.php @@ -5,6 +5,7 @@ use Exception; use GF_Field; use GFCommon; +use GFPDF\Controller\Controller_PDF; use GFPDF\Helper\Fields\Field_Products; use GFPDF\Helper\Helper_Abstract_Fields; use GFPDF\Helper\Helper_Abstract_Form; @@ -19,6 +20,7 @@ use GFPDF\Helper\Helper_Misc; use GFPDF\Helper\Helper_PDF; use GFPDF\Helper\Helper_Templates; +use GFPDF\Statics\Debug; use GFPDF\Statics\Kses; use GFPDFEntryDetail; use GWConditionalLogicDateFields; @@ -42,6 +44,8 @@ * A general class for PDF display * * @since 4.0 + * + * @method Controller_PDF getController */ class View_PDF extends Helper_Abstract_View { @@ -139,31 +143,32 @@ public function __construct( array $data_cache, Helper_Abstract_Form $gform, Log } /** - * Our PDF Generator + * Legacy view/download PDF generator * * @param array $entry The Gravity Forms Entry to process * @param array $settings The Gravity Form PDF Settings * * @return void - * - * @since 4.0 + * @since 4.0 + * @deprecated 6.12.0 Use \GPDFAPI::create_pdf() to generate PDFs */ public function generate_pdf( $entry, $settings ) { + _doing_it_wrong( __METHOD__, 'Use \GPDFAPI::create_pdf() to generate PDFs', '6.12' ); $controller = $this->getController(); $model = $controller->model; $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); - $form = $this->add_gravity_perk_conditional_logic_date_support( $form ); - /** - * Set out our PDF abstraction class - */ - $pdf = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates, $this->log ); - $pdf->set_filename( $model->get_pdf_name( $settings, $entry ) ); + do_action( 'gfpdf_view_or_download_pdf', $form, $entry, $settings ); + + $settings['pdf_action'] = apply_filters( 'gfpdfe_pdf_output_type', $settings['pdf_action'] ?? 'download' ); /* Backwards compat */ - $this->fix_wp_external_links_plugin_conflict(); + /* Setup the PDF that will be generated */ + $pdf_generator = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates, $this->log ); + $pdf_generator->set_filename( $model->get_pdf_name( $settings, $entry ) ); + $pdf_generator = apply_filters( 'gfpdf_pdf_generator_pre_processing', $pdf_generator ); - do_action( 'gfpdf_pre_pdf_generation', $form, $entry, $settings, $pdf ); + do_action( 'gfpdf_pre_pdf_generation', $form, $entry, $settings, $pdf_generator ); /** * Load our arguments that should be accessed by our PDF template @@ -181,43 +186,40 @@ public function generate_pdf( $entry, $settings ) { ); /* Show $form_data array if requested */ - $this->maybe_view_form_data( $args['form_data'] ?? [] ); - - /* Enable Multicurrency support */ - $this->misc->maybe_add_multicurrency_support(); + if ( $this->maybe_view_form_data() ) { + $this->view_form_data( $args['form_data'] ?? [] ); + } try { /* Initialise our PDF helper class */ - $pdf->init(); - $pdf->set_template(); + $pdf_generator->init(); + $pdf_generator->set_template(); /* Set display type and allow user to override the behaviour */ - $settings['pdf_action'] = apply_filters( 'gfpdfe_pdf_output_type', $settings['pdf_action'] ); /* Backwards compat */ if ( $settings['pdf_action'] === 'download' ) { - $pdf->set_output_type( 'download' ); + $pdf_generator->set_output_type( 'download' ); } /* Add Backwards compatibility support for our v3 Tier 2 Add-on */ if ( isset( $settings['advanced_template'] ) && strtolower( $settings['advanced_template'] ) === 'yes' ) { /* Check if we should process this document using our legacy system */ - if ( $model->handle_legacy_tier_2_processing( $pdf, $entry, $settings, $args ) ) { + if ( $model->handle_legacy_tier_2_processing( $pdf_generator, $entry, $settings, $args ) ) { return; } } /* Determine if we should show the print dialog box */ - /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ - if ( isset( $_GET['print'] ) ) { - $pdf->set_print_dialog(); + if ( rgget( 'print' ) ) { + $pdf_generator->set_print_dialog(); } /* Render the PDF template HTML */ - $pdf->render_html( $args ); + $pdf_generator->render_html( $args ); /* Generate PDF */ - $pdf->generate(); + $pdf_generator->generate(); } catch ( Exception $e ) { @@ -245,43 +247,6 @@ public function generate_pdf( $entry, $settings ) { } } - /** - * The WP External Links plugin conflicts with Gravity PDF when trying to display the PDF - * This method disables the \WPEL_Front::scan() method which was causes the problem - * - * See https://github.com/GravityPDF/gravity-pdf/issues/386 - * - * @since 4.1 - */ - private function fix_wp_external_links_plugin_conflict() { - if ( function_exists( 'wpel_init' ) ) { - add_filter( - 'wpel_apply_settings', - function() { - return false; - } - ); - } - } - - /** - * Add Gravity Perk Conditional Logic Date Field support, if required - * - * @Internal Fixed an intermittent issue with the Product table not functioning correctly - * - * @param array $form - * - * @return array - * @since 4.5 - */ - private function add_gravity_perk_conditional_logic_date_support( $form ) { - if ( method_exists( 'GWConditionalLogicDateFields', 'convert_conditional_logic_date_field_values' ) ) { - $form = GWConditionalLogicDateFields::convert_conditional_logic_date_field_values( $form ); - } - - return $form; - } - /** * Ensure a PHP extension is added to the end of the template name * @@ -290,13 +255,12 @@ private function add_gravity_perk_conditional_logic_date_support( $form ) { * @return string * * @since 4.0 + * @deprecated 4.1 */ public function get_template_filename( $name ) { - if ( substr( $name, -4 ) !== '.php' ) { - $name = $name . '.php'; - } + _doing_it_wrong( __METHOD__, 'This method has been replaced by Helper_Misc::get_file_with_extension().', '4.1' ); - return $name; + return $this->misc->get_file_with_extension( $name, '.php' ); } /** @@ -661,29 +625,31 @@ public function allow_pdf_css( $styles ) { } /** + * Check if the form data should be viewed during this request + * * @param array $form_data * - * @return void + * @return bool * * @since 6.4.0 */ - public function maybe_view_form_data( $form_data ) { - - /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ - if ( ! isset( $_GET['data'] ) ) { - return; - } - - /* Disable if PDF Debug Mode off AND the environment is production */ - if ( $this->options->get_option( 'debug_mode', 'No' ) === 'No' && ( ! function_exists( 'wp_get_environment_type' ) || wp_get_environment_type() === 'production' ) ) { - return; - } - - /* Check if user has permission to view info */ - if ( ! $this->gform->has_capability( 'gravityforms_view_settings' ) ) { - return; - } + public function maybe_view_form_data( $form_data = [] ) { + return rgget( 'data' ) && Debug::is_enabled_and_can_view(); + } + /** + * A debugging tool that will display the processed Gravity Forms entry array (aka $form_data) in the browser + * + * Use ?data=1 to active when the website is in development/staging mode and the current logged-in + * user has appropriate capabilities + * + * @param array $form_data + * + * @since 6.11.0 + * + * @link https://docs.gravitypdf.com/v6/developers/helper-parameters#data1 + */ + public function view_form_data( $form_data ) { print '
';
 		/* phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r */
 		print_r( $form_data );
diff --git a/src/deprecated.php b/src/deprecated.php
index 8e709aabf..df07f3494 100644
--- a/src/deprecated.php
+++ b/src/deprecated.php
@@ -680,7 +680,7 @@ class GFPDF_Core_Model extends GFPDF_Deprecated_Abstract {
 	 * @since 3.0
 	 */
 	public static function gfpdfe_save_pdf( $entry, $form ) {
-		$pdfs = GPDFAPI::get_form_pdfs( $form['id'] );
+		$pdfs = GPDFAPI::get_entry_pdfs( $entry['id'] );
 
 		if ( ! is_wp_error( $pdfs ) ) {
 			foreach ( $pdfs as $pdf ) {
diff --git a/tests/e2e/advanced-checks/confirmation-shortcode.test.js b/tests/e2e/advanced-checks/confirmation-shortcode.test.js
index df73cdc7e..63bb3459c 100644
--- a/tests/e2e/advanced-checks/confirmation-shortcode.test.js
+++ b/tests/e2e/advanced-checks/confirmation-shortcode.test.js
@@ -41,7 +41,7 @@ test('should check shortcode confirmation type TEXT is working correctly', async
 
   // Assertions
   await t
-    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"')).ok()
+    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"; filename*=utf-8\'\'Sample.pdf')).ok()
     .expect(downloadLogger.contains(r => r.response.headers['content-type'] === 'application/pdf')).ok()
 })
 
@@ -96,7 +96,7 @@ test('should check if the shortcode confirmation type PAGE is working correctly'
 
   // Assertions
   await t
-    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"')).ok()
+    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"; filename*=utf-8\'\'Sample.pdf')).ok()
     .expect(downloadLogger.contains(r => r.response.headers['content-type'] === 'application/pdf')).ok()
 })
 
@@ -125,7 +125,7 @@ test('should check if the shortcode confirmation type REDIRECT download is worki
 
   // Assertions
   await t
-    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"')).ok()
+    .expect(downloadLogger.contains(r => r.response.headers['content-disposition'] === 'attachment; filename="Sample.pdf"; filename*=utf-8\'\'Sample.pdf')).ok()
     .expect(downloadLogger.contains(r => r.response.headers['content-type'] === 'application/pdf')).ok()
 })
 
diff --git a/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php b/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php
index 103921f40..0f415e75d 100644
--- a/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php
+++ b/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php
@@ -5,6 +5,7 @@
 use Exception;
 use GFPDF\Controller\Controller_Pdf_Queue;
 use GFPDF\Helper\Helper_Pdf_Queue;
+use GFPDF\Statics\Cache;
 use GFPDF\Statics\Queue_Callbacks;
 use WP_UnitTestCase;
 
@@ -291,15 +292,12 @@ public function test_queue_async_form_submission_tasks() {
 
 		$this->assertCount( 3, $queue[0] );
 		$this->assertCount( 3, $queue[1] );
-		$this->assertCount( 1, $queue[2] );
 
 		$actions = [ 'create_pdf', 'create_pdf', 'send_notification' ];
 		for ( $i = 0; $i < 3; $i++ ) {
 			$this->assertStringContainsString( $actions[ $i ], $queue[0][ $i ]['func'] );
 			$this->assertStringContainsString( $actions[ $i ], $queue[1][ $i ]['func'] );
 		}
-
-		$this->assertStringContainsString( 'cleanup_pdfs', $queue[2][0]['func'] );
 	}
 
 	/**
@@ -322,14 +320,11 @@ public function test_queue_async_resend_notification_tasks() {
 		$queue = $this->queue_mock->get_data();
 
 		$this->assertCount( 3, $queue[0] );
-		$this->assertCount( 1, $queue[1] );
 
 		$actions = [ 'create_pdf', 'create_pdf', 'send_notification' ];
 		for ( $i = 0; $i < 3; $i++ ) {
 			$this->assertStringContainsString( $actions[ $i ], $queue[0][ $i ]['func'] );
 		}
-
-		$this->assertStringContainsString( 'cleanup_pdfs', $queue[1][0]['func'] );
 	}
 
 	/**
@@ -359,21 +354,26 @@ public function test_queue_dispatch_resend_notification_tasks() {
 	 * @since 5.0
 	 */
 	public function test_cleanup_pdfs() {
-		global $gfpdf;
+		$this->setExpectedIncorrectUsage( 'GFPDF\Statics\Queue_Callbacks::cleanup_pdfs');
+		$this->setExpectedIncorrectUsage( 'GFPDF\Model\Model_PDF::cleanup_pdf');
+
+		$form_class = \GPDFAPI::get_form_class();
 
 		$results = $this->create_form_and_entries();
 		$entry   = $results['entry'];
-		$form    = $results['form'];
+		$form    = $form_class->get_form( $results['form']['id'] );  /* get from the database so the date created is accurate */
+
+		$path = Cache::get_path( $form, $entry, $form['gfpdf_form_settings']['556690c67856b'] );
+		$file   = "test-{$form['id']}.pdf";
 
-		$path = $gfpdf->data->template_tmp_location . $entry['form_id'] . $entry['id'] . '556690c67856b/';
 		wp_mkdir_p( $path );
-		$test_file = $path . 'file';
-		touch( $test_file );
-		$this->assertFileExists( $test_file );
+		touch( $path . $file );
+
+		$this->assertFileExists( $path . $file );
 
 		Queue_Callbacks::cleanup_pdfs( $form['id'], $entry['id'] );
 
-		$this->assertFileDoesNotExist( $test_file );
+		$this->assertFileDoesNotExist( $path . $file );
 		$this->assertFileDoesNotExist( $path );
 	}
 }
diff --git a/tests/phpunit/unit-tests/test-pdf.php b/tests/phpunit/unit-tests/test-pdf.php
index 0b1c3c0e2..59f1b6d71 100644
--- a/tests/phpunit/unit-tests/test-pdf.php
+++ b/tests/phpunit/unit-tests/test-pdf.php
@@ -13,6 +13,7 @@
 use GFPDF\Helper\Helper_Url_Signer;
 use GFPDF\Model\Model_PDF;
 use GFPDF\Plugins\DeveloperToolkit\Loader\Helper;
+use GFPDF\Statics\Cache;
 use GFPDF\View\View_PDF;
 use GPDFAPI;
 use ReflectionMethod;
@@ -121,17 +122,6 @@ public function test_actions() {
 			)
 		);
 		$this->assertSame( 10, has_action( 'gform_after_submission', [ $this->model, 'maybe_save_pdf' ] ) );
-		$this->assertSame( 9999, has_action( 'gform_after_submission', [ $this->model, 'cleanup_pdf' ] ) );
-		$this->assertSame(
-			9999,
-			has_action(
-				'gform_after_update_entry',
-				[
-					$this->model,
-					'cleanup_pdf_after_submission',
-				]
-			)
-		);
 		$this->assertSame( 10, has_action( 'gfpdf_cleanup_tmp_dir', [ $this->model, 'cleanup_tmp_dir' ] ) );
 	}
 
@@ -183,16 +173,6 @@ public function test_filters() {
 
 		/* Backwards compatibility */
 		$this->assertSame( 1, has_filter( 'gfpdfe_pre_load_template', [ 'PDFRender', 'prepare_ids' ] ) );
-		$this->assertSame(
-			10,
-			has_filter(
-				'gform_before_resend_notifications',
-				[
-					$this->model,
-					'resend_notification_pdf_cleanup',
-				]
-			)
-		);
 	}
 
 	/**
@@ -222,7 +202,7 @@ public function test_process_pdf_endpoint() {
 		try {
 			$this->controller->process_pdf_endpoint();
 		} catch ( Exception $e ) {
-			$this->assertEquals( 'There was a problem generating your PDF', $e->getMessage() );
+			$this->assertEquals( 'There was a problem creating the PDF', $e->getMessage() );
 
 			return;
 		}
@@ -236,6 +216,8 @@ public function test_process_pdf_endpoint() {
 	 * @since 4.0
 	 */
 	public function test_process_legacy_pdf_endpoint() {
+		$this->setExpectedIncorrectUsage( 'GFPDF\Controller\Controller_PDF::process_legacy_pdf_endpoint');
+		$this->setExpectedIncorrectUsage( 'GFPDF\Model\Model_PDF::get_legacy_config');
 
 		/* Force a failure */
 		$this->assertNull( $this->controller->process_legacy_pdf_endpoint() );
@@ -249,7 +231,7 @@ public function test_process_legacy_pdf_endpoint() {
 		try {
 			$results = $this->controller->process_legacy_pdf_endpoint();
 		} catch ( Exception $e ) {
-			$this->assertEquals( 'There was a problem generating your PDF', $e->getMessage() );
+			$this->assertEquals( 'There was a problem creating the PDF', $e->getMessage() );
 
 			return;
 		}
@@ -300,7 +282,7 @@ public function test_pdf_error() {
 			/* Do nothing here */
 		}
 
-		$this->assertEquals( 'There was a problem generating your PDF', $e->getMessage() );
+		$this->assertEquals( 'There was a problem creating the PDF', $e->getMessage() );
 
 		/* Authorise the current user and check the message is displayed correctly */
 		$user_id = $this->factory->user->create( [ 'role' => 'administrator' ] );
@@ -1051,17 +1033,15 @@ public function provider_get_active_pdfs() {
 	 * @since 4.0
 	 */
 	public function test_notifications() {
-		global $gfpdf;
+		$form_class = \GPDFAPI::get_form_class();
 
 		/* Setup some test data */
 		$results = $this->create_form_and_entries();
 		$entry   = $results['entry'];
-		$form    = $results['form'];
-		$form['gfpdf_form_settings'] = [ $form['gfpdf_form_settings']['556690c67856b'] ];
+		$form    = $form_class->get_form( $results['form']['id'] );  /* get from the database so the date created is accurate */
 
 		/* Create PDF file so it isn't recreated */
-		$folder = $form['id'] . $entry['id'] . '556690c67856b';
-		$path   = $gfpdf->data->template_tmp_location . "$folder/";
+		$path = Cache::get_path( $form, $entry, $form['gfpdf_form_settings']['556690c67856b'] );
 		$file   = "test-{$form['id']}.pdf";
 
 		wp_mkdir_p( $path );
@@ -1070,7 +1050,7 @@ public function test_notifications() {
 		$notifications = $this->model->notifications( $form['notifications']['54bca349732b8'], $form, $entry );
 
 		/* Check the results are successful */
-		$this->assertStringContainsString( "PDF_EXTENDED_TEMPLATES/tmp/$folder/$file", $notifications['attachments'][0] );
+		$this->assertEquals( $path . $file, $notifications['attachments'][0] );
 
 		/* Clean up */
 		unlink( $notifications['attachments'][0] );
@@ -1266,12 +1246,12 @@ public function test_cleanup_tmp_dir() {
 		/* Create our files to test */
 		$files = [
 			'test'      => time(),
-			'test1'     => time() - ( 11.5 * 3600 ),
-			'test2'     => time() - ( 12.01 * 3600 ),
-			'test3'     => time() - ( 12.5 * 3600 ),
+			'test1'     => time() - ( 1 * 3600 ),
+			'test2'     => time() - ( 1.01 * 3600 ),
+			'test3'     => time() - ( 1.1 * 3600 ),
 			'test4'     => time() - ( 25 * 3600 ),
 			'test5'     => time() - ( 15 * 3600 ),
-			'test6'     => time() - ( 5 * 3600 ),
+			'test6'     => time() - ( 0.25 * 3600 ),
 			'.htaccess' => time() - ( 48 * 3600 ),
 			'mpdf/test' => time() - ( 25 * 3600 ), /* normally deleted, but excluded */
 		];
@@ -1280,7 +1260,7 @@ public function test_cleanup_tmp_dir() {
 			touch( $tmp . $file, $modified );
 		}
 
-		/* Run our cleanup function and test the out put */
+		/* Run our cleanup function and test the output */
 		$this->model->cleanup_tmp_dir();
 
 		$this->assertFileExists( $tmp . 'test' );
@@ -1305,22 +1285,26 @@ public function test_cleanup_tmp_dir() {
 	 * @since 4.0
 	 */
 	public function test_cleanup_pdf() {
-		global $gfpdf;
+		$this->setExpectedIncorrectUsage('GFPDF\Model\Model_PDF::cleanup_pdf');
+
+		$form_class = \GPDFAPI::get_form_class();
 
 		/* Setup some test data */
 		$results = $this->create_form_and_entries();
 		$entry   = $results['entry'];
-		$form    = $results['form'];
-		$file    = $gfpdf->data->template_tmp_location . "{$form['id']}{$entry['id']}556690c67856b/test-{$form['id']}.pdf";
+		$form    = $form_class->get_form( $results['form']['id'] );  /* get from the database so the date created is accurate */
+
+		$path = Cache::get_path( $form, $entry, $form['gfpdf_form_settings']['556690c67856b'] );
+		$file   = "test-{$form['id']}.pdf";
 
-		wp_mkdir_p( dirname( $file ) );
-		touch( $file );
+		wp_mkdir_p( $path );
+		touch( $path . $file );
 
-		$this->assertFileExists( $file );
+		$this->assertFileExists( $path . $file );
 
 		$this->model->cleanup_pdf( $entry, $form );
 
-		$this->assertFileDoesNotExist( $file );
+		$this->assertFileDoesNotExist( $path . $file );
 	}
 
 	/**
@@ -1520,6 +1504,7 @@ public function test_get_field_class() {
 	 * @since 4.0
 	 */
 	public function test_get_legacy_config() {
+		$this->setExpectedIncorrectUsage('GFPDF\Model\Model_PDF::get_legacy_config');
 
 		/* Setup some test data */
 		$results = $this->create_form_and_entries();
@@ -1550,6 +1535,7 @@ public function test_get_legacy_config() {
 	 * @dataProvider provider_get_template_filename
 	 */
 	public function test_get_template_filename( $expected, $template ) {
+		$this->setExpectedIncorrectUsage('GFPDF\View\View_PDF::get_template_filename');
 		$this->assertEquals( $expected, $this->view->get_template_filename( $template ) );
 	}
 
diff --git a/tests/phpunit/unit-tests/test-slow-pdf-processes.php b/tests/phpunit/unit-tests/test-slow-pdf-processes.php
index cd576e195..78df6d2c5 100644
--- a/tests/phpunit/unit-tests/test-slow-pdf-processes.php
+++ b/tests/phpunit/unit-tests/test-slow-pdf-processes.php
@@ -7,6 +7,7 @@
 use GFPDF\Helper\Helper_PDF;
 use GFPDF\Helper\Helper_Url_Signer;
 use GFPDF\Model\Model_PDF;
+use GFPDF\Statics\Cache;
 use GFPDF\View\View_PDF;
 use GFPDF_Core_Model;
 use GPDFAPI;
@@ -155,15 +156,20 @@ public function test_process_pdf() {
 		/* Disable all middleware and check if PDF generation begins */
 		remove_all_filters( 'gfpdf_pdf_middleware' );
 
-		try {
-			$this->model->process_pdf( $pid, $lid );
-		} catch ( Exception $e ) {
-			$this->assertEquals( 'There was a problem generating your PDF', $e->getMessage() );
-
-			return;
+		/* Verify the PDF generation begins and then fails as expected */
+		$results = $this->model->process_pdf( $pid, $lid );
+		if ( ! is_wp_error( $results ) ) {
+			$this->fail( 'This test did not fail as expected' );
 		}
 
-		$this->fail( 'This test did not fail as expected' );
+		/*
+		 * Prior to 6.12 $this->model->process_pdf() would call $this->view->generate_pdf()
+		 * and any errors would be output via wp_die(), which could be caught as an exception
+		 * in PHPUnit. Now that process_pdf() runs through $this->model->generate_and_save_pdf()
+		 * any errors are returned back up the chain for $this->controller->process_pdf_endpoint() to handle.
+		 * This is the reason this unit test was modified to explicitly check is_wp_error().
+		 */
+		$this->assertEquals( 'pdf_generation_failure', $results->get_error_code() );
 	}
 
 	/**
@@ -201,25 +207,29 @@ public function test_process_and_save_pdf() {
 	public function test_maybe_save_pdf() {
 		global $gfpdf;
 
+		$form_class = \GPDFAPI::get_form_class();
+
 		/* Setup some test data */
 		$results = $this->create_form_and_entries();
 		$entry   = $results['entry'];
-		$form    = $results['form'];
-		$file    = $gfpdf->data->template_tmp_location . "{$form['id']}{$entry['id']}556690c67856b/test-{$form['id']}.pdf";
+		$form    = $form_class->get_form( $results['form']['id'] );  /* get from the database so the date created is accurate */
+
+		$path = Cache::get_path( $form, $entry, $form['gfpdf_form_settings']['556690c67856b'] );
+		$file = "test-{$form['id']}.pdf";
 
 		$this->model->maybe_save_pdf( $entry, $form );
 
 		/* Check the results are successful */
-		$this->assertFileExists( $file );
+		$this->assertFileExists( $path . $file );
 
 		/* Clean up */
-		unlink( $file );
+		unlink( $path . $file );
 
 		/* Ensure function doesn't run when background processing enabled */
 		$gfpdf->options->update_option( 'background_processing', 'Yes' );
 
 		$this->model->maybe_save_pdf( $entry, $form );
-		$this->assertFileDoesNotExist( $file );
+		$this->assertFileDoesNotExist( $path . $file );
 	}
 
 	/**
@@ -230,6 +240,8 @@ public function test_maybe_save_pdf() {
 	 * @since 4.0
 	 */
 	public function test_generate_pdf() {
+		$this->setExpectedIncorrectUsage( 'GFPDF\View\View_PDF::generate_pdf');
+
 		global $gfpdf;
 
 		/* Setup our form and entries */
@@ -407,21 +419,27 @@ function( $settings ) {
 	 * works as expected.
 	 */
 	public function test_deprecated_save_pdf() {
-		global $gfpdf;
+		$form_class = \GPDFAPI::get_form_class();
 
 		$results = $this->create_form_and_entries();
 		$entry   = $results['entry'];
-		$form    = $results['form'];
+		$form    = $form_class->get_form( $results['form']['id'] );  /* get from the database so the date created is accurate */
 
-		$filename = $gfpdf->data->template_tmp_location . "11556690c67856b/test-{$form['id']}.pdf";
-
-		if ( is_file( $filename ) ) {
-			unlink( $filename );
-		}
+		$filename = "test-{$form['id']}.pdf";
 
 		GFPDF_Core_Model::gfpdfe_save_pdf( $entry, $form );
-		$this->assertTrue( is_file( $filename ) );
 
-		unlink( $filename );
+		$pdfs = GPDFAPI::get_entry_pdfs( $entry['id'] );
+		foreach ( $pdfs as $pdf ) {
+			/* Skip non-core PDFs */
+			if ( ! in_array( $pdf['template'], [ 'zadani', 'focus-gravity', 'rubix', 'blank-slate' ], true ) ) {
+				continue;
+			}
+
+			/* Get PDF directory path from cache */
+			$path = Cache::get_path( $form, $entry, $pdf );
+			$this->assertFileExists( $path . $filename );
+			unlink( $path . $filename );
+		}
 	}
 }