From 0b1033e3c5218d2b24f0f03670ed0f85d67b67d9 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Wed, 17 Jul 2024 12:36:43 +1000 Subject: [PATCH] Continue cleaning up the code --- api.php | 42 +++++++++++ src/Controller/Controller_Cache.php | 6 +- src/Controller/Controller_PDF.php | 79 +++++++++++++++++++- src/Helper/Helper_PDF.php | 20 +++-- src/Model/Model_Cache.php | 2 +- src/Model/Model_PDF.php | 111 +++++++++++++++++++++------- src/Statics/Cache.php | 4 +- src/View/View_PDF.php | 1 + 8 files changed, 223 insertions(+), 42 deletions(-) diff --git a/api.php b/api.php index fe842ebd1..e6a2dbdfa 100644 --- a/api.php +++ b/api.php @@ -351,6 +351,48 @@ public static function delete_pdf( $form_id, $pdf_id ) { return $options->delete_pdf( $form_id, $pdf_id ); } + /** + * Retrieve the filename of the PDF document + * The .pdf extension is not returned by default (pass true to the 3rd param) + * + * See https://docs.gravitypdf.com/v6/developers/api/get_pdf_filename/ for more information about this method + * + * @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 $include_extension Whether to include the .pdf extension in the filename + * + * @return string|WP_Error Return the filename of the PDF, or a WP_Error on failure + * + * @return string|WP_Error + * + * @since 6.12 + */ + public static function get_pdf_filename( $entry_id, $pdf_id, bool $include_extension ) { + $form_class = static::get_form_class(); + + /* Get our entry */ + $entry = $form_class->get_entry( $entry_id ); + if ( is_wp_error( $entry ) ) { + return new WP_Error( 'invalid_entry', esc_html__( 'Make sure to pass in a valid Gravity Forms Entry ID', 'gravity-forms-pdf-extended' ) ); + } + + /* Get our settings */ + $setting = static::get_pdf( $entry['form_id'], $pdf_id ); + if ( is_wp_error( $setting ) ) { + 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' ) ); + } + + /** @var \GFPDF\Model\Model_PDF $pdf */ + $pdf = static::get_mvc_class( 'Model_PDF' ); + + $filename = $pdf->get_pdf_name( $setting, $entry ); + if ( $include_extension ) { + $filename .= '.pdf'; + } + + return $filename; + } + /** * Retrieve an array of the global Gravity PDF settings (this doesn't include individual form configuration details - see GPDFAPI::get_form_pdfs) * diff --git a/src/Controller/Controller_Cache.php b/src/Controller/Controller_Cache.php index d3965be59..c748230e4 100644 --- a/src/Controller/Controller_Cache.php +++ b/src/Controller/Controller_Cache.php @@ -6,10 +6,10 @@ class Controller_Cache { public function init() { //gfpdf_pre_pdf_generation -> view/download PDF -// View_PDF::maybe_view_form_data() disables cache -// Handle "print" option ($_GET['print']) or [gravitypdf print="1"] or {Report:pdf:6063bd0362dda:print} + // View_PDF::maybe_view_form_data() disables cache + // Handle "print" option ($_GET['print']) or [gravitypdf print="1"] or {Report:pdf:6063bd0362dda:print} -// $pdf_override = apply_filters( 'gfpdf_override_pdf_bypass', false, $pdf ); <-- bypass cache + // $pdf_override = apply_filters( 'gfpdf_override_pdf_bypass', false, $pdf ); <-- bypass cache } } diff --git a/src/Controller/Controller_PDF.php b/src/Controller/Controller_PDF.php index c86877b72..aa335eb8c 100644 --- a/src/Controller/Controller_PDF.php +++ b/src/Controller/Controller_PDF.php @@ -219,9 +219,10 @@ public function add_filters() { 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 ); + // @TODO + //add_filter( 'gform_before_resend_notifications', [ $this->model, 'resend_notification_pdf_cleanup' ], 10, 2 ); - /* Third Party Conflict Fixes */ + /* Backwards compatibility and 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( @@ -231,6 +232,72 @@ function() { } ); + add_filter( + 'gfpdf_current_pdf_settings_object', + function( $pdf_settings, $form, $entry ) { + return $this->model->apply_backwards_compatibility_filters( $pdf_settings, $entry ); + }, + 10, + 3 + ); + + /* Force PDF cache bypass if the user wants to view the mark-up */ + add_action( + 'gfpdf_view_or_download_pdf', + function() { + + /* @TODO - move to shared file */ + $options = \GPDFAPI::get_options_class(); + + /* 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; + } + + /* Bypass cache */ + add_filter( 'gfpdf_override_pdf_bypass', '__return_true' ); + } + ); + + /* Display form data if the user wants to view it */ + add_action( + 'gfpdf_view_or_download_pdf', + function( $form, $entry ) { + + /* @TODO - move to shared file */ + $options = \GPDFAPI::get_options_class(); + + if ( ! rgget( 'data' ) ) { + 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 */ + /* @TODO - correct permission? */ + if ( ! $this->gform->has_capability( 'gravityforms_view_settings' ) ) { + return; + } + + $this->view->view_form_data( \GPDFAPI::get_form_data( $entry['id'] ) ); + }, + 10, + 2 + ); + /* Meta boxes */ add_filter( 'gform_entry_detail_meta_boxes', [ $this->model, 'register_pdf_meta_box' ], 10, 3 ); @@ -350,7 +417,7 @@ public function remove_pre_pdf_hooks() { } /** - * Prevent the PDF Endpoints being indexed + * Try to prevent the PDF being indexed/cached * * @since 5.2 */ @@ -358,6 +425,10 @@ public function prevent_index() { if ( ! headers_sent() ) { header( 'X-Robots-Tag: noindex, nofollow', true ); } + + if ( ! defined( 'DONOTCACHEPAGE' ) ) { + define( 'DONOTCACHEPAGE', true ); + } } /** @@ -422,7 +493,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/Helper/Helper_PDF.php b/src/Helper/Helper_PDF.php index 0513f0a14..10d97bb74 100644 --- a/src/Helper/Helper_PDF.php +++ b/src/Helper/Helper_PDF.php @@ -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'] ) ); } /** @@ -284,6 +285,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 +521,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 +541,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 * @@ -868,13 +880,9 @@ protected function load_html( $args = [] ) { */ protected function maybe_display_raw_html( $html ) { + /* @TODO - move to shared file */ $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; diff --git a/src/Model/Model_Cache.php b/src/Model/Model_Cache.php index 6165421dd..ab25eba61 100644 --- a/src/Model/Model_Cache.php +++ b/src/Model/Model_Cache.php @@ -10,4 +10,4 @@ */ class Model_Cache extends Helper_Abstract_Model { -} \ No newline at end of file +} diff --git a/src/Model/Model_PDF.php b/src/Model/Model_PDF.php index c5edf4d00..0f683a1c0 100644 --- a/src/Model/Model_PDF.php +++ b/src/Model/Model_PDF.php @@ -176,7 +176,7 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { $this->log->error( 'Invalid Entry', [ - 'entry_id' => $lid, + 'entry_id' => $lid, 'WP_Error_Message' => $entry->get_error_message(), 'WP_Error_Code' => $entry->get_error_code(), ] @@ -223,7 +223,7 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { * * If any of the filters return a WP_Error object the request will not be fulfilled * - * Refer to https://docs.gravitypdf.com/v6/developers/filters/gfpdf_pdf_middleware/ for more details + * Refer to https://docs.gravitypdf.com/v6/developers/filters/gfpdf_pdf_middleware/ */ $middleware = apply_filters( 'gfpdf_pdf_middleware', false, $entry, $settings ); if ( is_wp_error( $middleware ) ) { @@ -246,25 +246,88 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { * 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', $settings['pdf_action'] ); /* Backwards compat */ + $action = apply_filters( 'gfpdfe_pdf_output_type', $action ); /* Backwards compat */ $action = in_array( $action, [ 'view', 'download' ], true ) ? $action : 'view'; - /* @TODO - add to hook */ - //$this->getController()->view->fix_wp_external_links_plugin_conflict(); + /* Get the PDF document for the request */ + $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); + + do_action( 'gfpdf_view_or_download_pdf', $form, $entry, $settings ); + + /* support the print dialog option */ + if ( rgget( 'print' ) === '1' ) { + $settings['print'] = true; /* force a new render */ + } + + $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 */ + header( + sprintf( + 'Content-disposition: %1$s; filename="%2$s"; filename*=utf-8\'\'%2$s', + $action === 'view' ? 'inline' : 'attachment', + rawurlencode( basename( $path_to_pdf ) ), + ) + ); - /* @TODO - add filter on `gfpdf_template_args` and output form data if needed */ -// if ( $this->getController()->view->maybe_view_form_data() ) { -// $this->getController()->view->view_form_data( $args['form_data'] ?? [] ); -// } + /* 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 ) ) ); + } - /* @TODO - add print setting via filter */ + /* Tell client to download the file */ + if ( $action === 'download' ) { + header( 'Content-Description: File Transfer' ); + header( 'Content-Transfer-Encoding: binary' ); + } - /* generate_and_save */ -// do_action( 'gfpdf_pre_generate_and_save_pdf_notification', $form, $entry, $settings, $notifications ); -// $filename = $this->generate_and_save_pdf( $entry, $settings ); -// do_action( 'gfpdf_post_generate_and_save_pdf_notification', $form, $entry, $settings, $notifications ); + /* Set appropriate headers for local browser caching */ + $last_modified_time = filemtime( $path_to_pdf ); + $etag = md5( $path_to_pdf ); /* the path includes a unique hash that automatically changes when a PDF does */ - /* Handle output */ + 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 ); + + exit; } /** @@ -1176,23 +1239,19 @@ public function maybe_attach_to_notification( $notification, $settings, $entry = */ public function generate_and_save_pdf( $entry, $pdf_settings ) { - $form = $this->gform->get_form( $entry['form_id'] ); - $entry = apply_filters( 'gfpdf_pdf_entry', $entry, $form, $pdf_settings); - $pdf_settings = apply_filters( 'gfpdf_pdf_settings', $pdf_settings, $form, $entry ); - - /* @TODO - add get_pdf_name() to GPDFAPI (rename) */ - - /* @TODO - apply filter: Add backwards compatibility support for certain settings */ - $pdf_settings = $this->apply_backwards_compatibility_filters( $pdf_settings, $entry ); + $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 ); 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( $this->get_pdf_name( $pdf_settings, $entry ) ); + $pdf_generator->set_filename( $filename ); $pdf_generator = apply_filters( 'gfpdf_pdf_generator_pre_processing', $pdf_generator ); if ( ! $this->process_and_save_pdf( $pdf_generator ) ) { - return new WP_Error( 'pdf_generation_failure', esc_html__( 'The PDF could not be saved.', 'gravity-forms-pdf-extended' ) ); + return new WP_Error( 'pdf_generation_failure', esc_html__( 'There was a problem creating the PDF', 'gravity-forms-pdf-extended' ) ); } do_action( 'gfpdf_post_generate_and_save_pdf', $form, $entry, $pdf_settings ); @@ -1239,7 +1298,7 @@ public function process_and_save_pdf( Helper_PDF $pdf_generator ) { /* Get required parameters */ $entry = $pdf_generator->get_entry(); $settings = $pdf_generator->get_settings(); - $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); + $form = $pdf_generator->get_form(); do_action( 'gfpdf_pre_pdf_generation', $form, $entry, $settings, $pdf_generator ); diff --git a/src/Statics/Cache.php b/src/Statics/Cache.php index 9ede7c76a..e58ee1054 100644 --- a/src/Statics/Cache.php +++ b/src/Statics/Cache.php @@ -38,7 +38,7 @@ protected static function get_basepath() { return self::$template_tmp_location; } - $data = \GPDFAPI::get_data_class(); + $data = \GPDFAPI::get_data_class(); $base_path = $data->template_tmp_location; if ( is_multisite() ) { $base_path .= get_current_blog_id(); @@ -48,4 +48,4 @@ protected static function get_basepath() { return self::$template_tmp_location; } -} \ No newline at end of file +} diff --git a/src/View/View_PDF.php b/src/View/View_PDF.php index 57eac7d2c..d080b0b57 100644 --- a/src/View/View_PDF.php +++ b/src/View/View_PDF.php @@ -681,6 +681,7 @@ public function allow_pdf_css( $styles ) { */ public function maybe_view_form_data( $form_data = [] ) { + /* @TODO - move to shared file */ /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ if ( ! isset( $_GET['data'] ) ) { return false;