diff --git a/api.php b/api.php index 58b9bd13a..f24f0d6eb 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,19 @@ 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', 9999 ); + } + + /** @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' ); + if ( $bypass_cache ) { + remove_filter( 'gfpdf_override_pdf_bypass', '__return_true', 9999 ); + } - return $filename; + return $path_to_pdf; } /** diff --git a/pdf.php b/pdf.php index 4425247e3..c0a620821 100644 --- a/pdf.php +++ b/pdf.php @@ -1,7 +1,7 @@ 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' ] ); + } + + /* Gravity Wiz Nested Forms support */ + if ( function_exists( 'gp_nested_forms' ) ) { + $included_nested_forms_in_cache_hash = function( $data, $form, $entry, $pdf_settings ) { + return $this->included_nested_forms_in_cache_hash( $data, $form, $entry, $pdf_settings ); + }; + + add_filter( 'gfpdf_cache_hash_array', $included_nested_forms_in_cache_hash, 10, 4 ); } /* Add Legal Signature support */ @@ -163,11 +161,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 +183,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 +192,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 +210,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 +254,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 +283,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 +292,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 +303,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 +315,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 +341,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 +356,38 @@ 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(); + + /* + * 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 +395,131 @@ 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 ) { + if ( ! isset( $form['id'] ) ) { + return $form; + } - /* Remove the shutdown buffer and manually close an open buffers */ - $minifier = Minifier::get_instance(); - remove_action( 'shutdown', [ $minifier, 'end_html_minifier_buffer' ] ); + /* Make Page fields first class citizens in the form object */ + $form = $this->model->register_page_fields( $form ); - while ( ob_get_level() > 0 ) { - ob_end_clean(); - } + /* 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; } /** - * Output PDF error to user + * Modify the PDF settings specifically for the PDF request + * + * @param array $pdf_settings + * @param array $form + * @param array $entry * - * @param Object $error The WP_Error object + * @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. To easily see the raw source code on screen use ?html=1&raw=1 + * + * @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; + } + + $html = apply_filters( 'gfpdf_pre_html_browser_output', $html, $pdf_settings, $entry, $form, $helper_pdf ); + + if ( rgget( 'raw' ) ) { + echo '
';
+			echo htmlspecialchars( $html ); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */
+			echo '
'; + } else { + echo $html; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ + } + + exit; + } + + /** + * 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 ); + } + + if ( ! defined( 'DONOTCACHEPAGE' ) ) { + define( 'DONOTCACHEPAGE', true ); + } + } + + /** + * Display appropriate error to user when PDF cannot be display + * + * @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 +554,41 @@ 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 } } + + /** + * If Nested Form fields are included in the form, include the child entries in the cache hash. + * This will auto-invalidate the parent PDF when the child entry is modified + * + * @param array $data Data to hash + * @param array $form Form object + * @param array $entry Entry object + * @param array $pdf_settings PDF object + * + * @return array + * + * @since 6.12 + */ + protected function included_nested_forms_in_cache_hash( $data, $form, $entry, $pdf_settings ) { + if ( empty( $entry['id'] ) ) { + return $data; + } + + if ( ! class_exists( '\GPNF_Entry' ) ) { + return $data; + } + + $parent_entry = new \GPNF_Entry( $entry ); + $child_entries = $parent_entry->get_child_entries(); + + if ( empty( $child_entries ) ) { + return $data; + } + + $data['nested_form_entries'] = $child_entries; + + return $data; + } } diff --git a/src/Controller/Controller_Pdf_Queue.php b/src/Controller/Controller_Pdf_Queue.php index cadd993b1..ceb7fcabe 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(); } @@ -231,7 +226,9 @@ public function queue_dispatch_resend_notification_tasks( $form, $entry ) { } /** - * Push tasks to the queue for requested notifications + * Push tasks to the queue for requested PDFs/Notifications + * + * Even if a PDF isn't attached to a notification, it may still need to be generated and saved to disk * * @param array $form * @param array $entry @@ -241,8 +238,10 @@ public function queue_dispatch_resend_notification_tasks( $form, $entry ) { * @since 6.11.0 */ public function queue_async_tasks( $form, $entry ) { - foreach ( $this->form_async_notifications as $notification ) { - $this->queue->push_to_queue( $this->get_queue_tasks( $entry, $form, [ $notification ] ) ); + $tasks = $this->get_queue_tasks( $entry, $form, $this->form_async_notifications ); + + if ( count( $tasks ) > 0 ) { + $this->queue->push_to_queue( $tasks ); } } @@ -255,17 +254,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' ); } /** @@ -342,7 +334,7 @@ protected function queue_pdfs( $notifications, $pdfs, $form, $entry ) { $pdf_queue_data = [ 'id' => $this->get_queue_id( $form, $entry, $pdf ), 'func' => '\GFPDF\Statics\Queue_Callbacks::create_pdf', - 'args' => [ $entry['id'], $pdf['id'] ], + 'args' => [ $entry['id'], $pdf['id'], get_current_user_id() ], 'unrecoverable' => true, ]; @@ -390,7 +382,7 @@ protected function queue_notifications( $notifications, $pdfs, $form, $entry ) { $queue_data[] = [ 'id' => $this->get_queue_id( $form, $entry, $pdf ) . '-' . $notification['id'], 'func' => '\GFPDF\Statics\Queue_Callbacks::send_notification', - 'args' => [ $form['id'], $entry['id'], $notification ], + 'args' => [ $form['id'], $entry['id'], $notification, get_current_user_id() ], ]; /* Only queue each notification once */ @@ -421,7 +413,7 @@ protected function is_notification_enabled( $notification_id, $form, $entry ) { } $notification = $form['notifications'][ $notification_id ]; - if ( empty( $notification['isActive'] ) ) { + if ( isset( $notification['isActive'] ) && ! $notification['isActive'] ) { return false; } @@ -477,6 +469,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..28412fb42 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'] ) ); } /** @@ -250,7 +251,7 @@ public function render_html( $args = [], $html = '' ) { $form = $this->form; - /* Allow this method to be short circuited */ + /* Allow this method to be short-circuited */ if ( apply_filters( 'gfpdf_skip_pdf_html_render', false, $args, $this ) ) { do_action( 'gfpdf_skipped_html_render', $args, $this ); @@ -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() { @@ -343,6 +342,8 @@ public function save_pdf( $raw_pdf_string ) { if ( ! wp_mkdir_p( $this->path ) ) { throw new Exception( sprintf( 'Could not create directory: %s', esc_html( $this->path ) ) ); } + + file_put_contents( $this->path . 'index.html', '' ); } /* save our PDF */ @@ -519,7 +520,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 +540,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 +595,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 +867,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 +880,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_Install.php b/src/Model/Model_Install.php index 8e24eda39..c871c8532 100644 --- a/src/Model/Model_Install.php +++ b/src/Model/Model_Install.php @@ -303,23 +303,20 @@ public function create_folder_structures() { $this->notices->add_error( sprintf( esc_html__( 'Gravity PDF does not have write permission to the %s directory. Contact your web hosting provider to fix the issue.', 'gravity-forms-pdf-extended' ), '' . $this->misc->relative_path( $dir ) . '' ) ); } } - } - /* create blank index file in all folders to prevent web servers listing the entire directory */ - if ( is_dir( $this->data->template_location ) && ! is_file( $this->data->template_location . 'index.html' ) ) { - GFCommon::recursive_add_index_file( $this->data->template_location ); + /* create blank index file in all folders to prevent web servers listing the entire directory */ + if ( ! is_file( trailingslashit( $dir ) . 'index.html' ) ) { + file_put_contents( trailingslashit( $dir ) . 'index.html', '' ); + } } /* create deny htaccess file to prevent direct access to files */ - if ( is_dir( $this->data->template_tmp_location ) ) { - if ( ! is_file( $this->data->template_tmp_location . 'index.html' ) ) { - GFCommon::recursive_add_index_file( $this->data->template_tmp_location ); - } - - if ( ! is_file( $this->data->template_tmp_location . '.htaccess' ) ) { - $this->log->notice( 'Create Apache .htaccess Security file' ); - file_put_contents( $this->data->template_tmp_location . '.htaccess', 'deny from all' ); - } + if ( + is_dir( $this->data->template_tmp_location ) && + ! is_file( $this->data->template_tmp_location . '.htaccess' ) + ) { + $this->log->notice( 'Create Apache .htaccess Security file' ); + file_put_contents( $this->data->template_tmp_location . '.htaccess', 'deny from all' ); } } diff --git a/src/Model/Model_PDF.php b/src/Model/Model_PDF.php index 89fd0c3a0..a4be20034 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 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 + * 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 * - * See https://docs.gravitypdf.com/v6/developers/filters/gfpdf_pdf_middleware/ for more details about this filter + * 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/ */ $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; + } - return null; + /* 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, $action ); + + /* 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 ) ), + ) + ); + + /* 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,136 @@ 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 + * + * @see \GPDFAPI::create_pdf() We recommend third-party developers use the API to generate PDFs */ - 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 +1498,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 +1515,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 +2044,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 +2082,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 +2112,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 +2150,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 +2265,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 +2513,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..8a4ebbdfa --- /dev/null +++ b/src/Statics/Cache.php @@ -0,0 +1,182 @@ +template_tmp_location; + if ( is_multisite() ) { + $base_path .= get_current_blog_id(); + } + + static::$template_tmp_location = trailingslashit( $base_path ) . 'cache/'; + + /* Create directory on disk if it does not exist */ + if ( ! is_dir( static::$template_tmp_location ) ) { + if ( wp_mkdir_p( static::$template_tmp_location ) ) { + file_put_contents( static::$template_tmp_location . 'index.html', '' ); + } + } + + return static::$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, $pdf_settings, user ID, site ID, or template files are changed a new hash and PDF will be generated + * + * @since 6.12.0 + */ + public static function get_hash( $form, $entry, $pdf_settings ) { + + /* + * Standardize field properties that may be added dynamically when fields are processed + * for the first run of a PDF. When Gravity Forms accesses properties that don't exist + * in a GF_Field object, it adds the value automatically and sets it to an empty string. + */ + array_map( + function( $field ) { + /** @var \GF_Field $field */ + /* Set when accessing \GFCommon::selection_display() */ + if ( in_array( $field->get_input_type(), [ 'checkbox', 'radio', 'select' ], true ) ) { + $field->enablePrice; + } + + /* Set when accessing \GF_Fields::get_allowable_tags() */ + if ( $field->get_input_type() === 'section' ) { + $field->form_id; + } + + /* Set the `use_admin_label` context for all fields */ + $field->set_context_property( 'use_admin_label', $field->get_context_property( 'use_admin_label' ) ); + }, + $form['fields'] + ); + + /* + * Ignore specific entry meta that is considered unimportant to PDFs + */ + $ignored_entry_meta = apply_filters( 'gfpdf_cache_hash_ignored_entry_meta', [ 'is_read', 'is_starred', 'is_approved', 'status', 'source_url', 'user_agent' ], $form, $entry, $pdf_settings ); + foreach ( $ignored_entry_meta as $meta ) { + unset( $entry[ $meta ] ); + } + + /* Add last modified date of template files to hash */ + $template = \GPDFAPI::get_templates_class(); + $template_id = $pdf_settings['template'] ?? ''; + + try { + $template_path = $template->get_template_path_by_id( $template_id ); + $template_timestamps = filemtime( $template_path ); + } catch ( \Exception $e ) { + $template_timestamps = 0; + } + + /* Include config template timestamp if it exists */ + try { + $template_config_path = $template->get_config_path_by_id( $template_id ); + $template_timestamps .= filemtime( $template_config_path ); + } catch ( \Exception $e ) { + /* do nothing */ + } + + /* Build an array of unique data relevant to the current PDF */ + $unique_array = apply_filters( + 'gfpdf_cache_hash_array', + [ + 'site_id' => get_current_blog_id(), + 'user_id' => get_current_user_id(), + 'fields' => $form['fields'], + 'entry' => $entry, + 'pdf_settings' => $pdf_settings, + 'template_last_updated' => $template_timestamps, + ], + $form, + $entry, + $pdf_settings + ); + + /* Generate the hash based on that unique data */ + $hash_prefix = static::get_hash_prefix( $form, $entry, $pdf_settings ); + $hash = wp_hash( wp_json_encode( $unique_array ) ); + + return sprintf( '%s-%s', $hash_prefix, $hash ); + } + + /** + * Gets the easily-identifiable prefix to add before the hash + * + * @param array $form The form object + * @param array $entry The entry object + * @param array $pdf_settings The PDF object/settings + * + * @return string + * + * @since 6.12 + */ + protected static function get_hash_prefix( $form, $entry, $pdf_settings ) { + return sprintf( + 's%1$d-f%2$d-e%3$d-p%4$s', + get_current_blog_id(), + $form['id'] ?? 0, + $entry['id'] ?? 0, + $pdf_settings['id'] ?? '', + ); + } +} 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..b2a4c7a30 100644 --- a/src/Statics/Queue_Callbacks.php +++ b/src/Statics/Queue_Callbacks.php @@ -31,20 +31,26 @@ class Queue_Callbacks { /** * Generate and save a PDF to disk * - * @param $entry_id - * @param $pdf_id + * @param int $entry_id Entry ID to process + * @param string $pdf_id PDF ID to process + * @param int $user_id User ID who triggered the queue * * @throws Exception * * @since 5.0 */ - public static function create_pdf( $entry_id, $pdf_id ) { + public static function create_pdf( $entry_id, $pdf_id, $user_id = 0 ) { $log = GPDFAPI::get_log_class(); + /* Masquerade as the user ID who scheduled the queue so caching and the {user} merge tag works correctly */ + $backup_user_id = get_current_user_id(); + wp_set_current_user( $user_id ); + /* For performance, only generate the PDF if it does not currently exist on disk */ - add_filter( 'gfpdf_override_pdf_bypass', '__return_false', 20 ); $pdf = GPDFAPI::create_pdf( $entry_id, $pdf_id ); - remove_filter( 'gfpdf_override_pdf_bypass', '__return_false', 20 ); + + /* Reset existing user */ + wp_set_current_user( $backup_user_id ); if ( is_wp_error( $pdf ) ) { $log->error( @@ -64,14 +70,15 @@ public static function create_pdf( $entry_id, $pdf_id ) { /** * Send a Gravity Forms notification * - * @param int $form_id - * @param int $entry_id - * @param array $notification + * @param int $entry_id Entry ID to process + * @param string $pdf_id PDF ID to process + * @param array $notification Gravity Forms Notification to send + * @param int $user_id User ID who triggered the queue * * @throws Exception * @since 5.0 */ - public static function send_notification( $form_id, $entry_id, $notification ) { + public static function send_notification( $form_id, $entry_id, $notification, $user_id = 0 ) { $log = GPDFAPI::get_log_class(); $gform = GPDFAPI::get_form_class(); @@ -96,7 +103,14 @@ public static function send_notification( $form_id, $entry_id, $notification ) { throw new Exception(); } + /* Masquerade as the user ID who scheduled the queue so caching and the {user} merge tag works correctly */ + $backup_user_id = get_current_user_id(); + wp_set_current_user( $user_id ); + GFCommon::send_notification( $notification, $form, $entry ); + + /* Reset existing user */ + wp_set_current_user( $backup_user_id ); } /** @@ -108,13 +122,13 @@ 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 ) { - $gform = GPDFAPI::get_form_class(); - $data = GPDFAPI::get_data_class(); - $misc = GPDFAPI::get_misc_class(); - $templates = GPDFAPI::get_templates_class(); - $log = GPDFAPI::get_log_class(); + _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(); + $log = GPDFAPI::get_log_class(); /** @var Model_PDF $model_pdf */ $model_pdf = GPDFAPI::get_mvc_class( 'Model_PDF' ); diff --git a/src/View/View_PDF.php b/src/View/View_PDF.php index c3f20c963..e70d83700 100644 --- a/src/View/View_PDF.php +++ b/src/View/View_PDF.php @@ -5,8 +5,8 @@ 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; use GFPDF\Helper\Helper_Abstract_Model; use GFPDF\Helper\Helper_Abstract_Options; @@ -19,9 +19,9 @@ 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; use Psr\Log\LoggerInterface; use WP_Error; @@ -42,6 +42,8 @@ * A general class for PDF display * * @since 4.0 + * + * @method Controller_PDF getController */ class View_PDF extends Helper_Abstract_View { @@ -139,31 +141,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 +184,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 +245,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 +253,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,33 +623,35 @@ 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(); + } - print '
';
+	/**
+	 * 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 ) {
+		echo '
';
 		/* phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r */
 		print_r( $form_data );
-		print '
'; + echo '
'; exit; } 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/e2e/utilities/page-model/helpers/page.js b/tests/e2e/utilities/page-model/helpers/page.js index 2f30e40d9..f4dfc8c1c 100644 --- a/tests/e2e/utilities/page-model/helpers/page.js +++ b/tests/e2e/utilities/page-model/helpers/page.js @@ -36,7 +36,7 @@ class Page { .click(this.addBlockIcon) .typeText(this.searchBlock, 'paragraph', { paste: true }) .click(this.paragraphButton) - .typeText(Selector('p.is-selected'), 'Content', { paste: true }) + .typeText(Selector('.is-root-container p:last-of-type'), 'Content', { paste: true }) .click(this.publishButton) .click(this.confirmPublishButton) } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 6216c343e..88e4ccc27 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -7,6 +7,9 @@ xdebug_disable(); } +/* Use different DB tables for unit tests, so we don't mess with the E2E environment */ +putenv( 'WORDPRESS_TABLE_PREFIX=phpunit_' ); + /** * Override certain pluggable functions so we can unit test them correctly * 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..399bdf8d3 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; @@ -278,28 +279,26 @@ public function test_queue_async_form_submission_tasks() { $results = $this->create_form_and_entries(); $entry = $results['entry']; $form = $results['form']; - $form['notifications']['1254123223'] = $form['notifications']['54bca349732b8']; + + /* Set up active and inactive notification */ + $form['notifications']['1254123223'] = [ 'id' => '1254123223', 'isActive' => true, 'event' => 'form_submission' ]; + $form['notifications']['2222222222'] = [ 'id' => '2222222222', 'isActive' => false, 'event' => 'form_submission' ]; + $form['gfpdf_form_settings']['556690c67856b']['notification'][] = '1254123223'; + $form['notifications']['54bca349732b8']['isActive'] = true; - foreach( $form['notifications'] as $notification ) { + foreach ( $form['notifications'] as $notification ) { $this->controller->maybe_disable_submission_notifications( false, $notification, $form, $entry ); } - + $this->controller->queue_async_form_submission_tasks( $entry, $form ); $queue = $this->queue_mock->get_data(); - $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'] ); + $this->assertStringContainsString( 'create_pdf', $queue[0][0]['func'] ); + $this->assertStringContainsString( 'create_pdf', $queue[0][1]['func'] ); + $this->assertStringContainsString( 'send_notification', $queue[0][2]['func'] ); + $this->assertStringContainsString( 'send_notification', $queue[0][3]['func'] ); } /** @@ -322,14 +321,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'] ); } /** @@ -343,12 +339,13 @@ public function test_queue_dispatch_resend_notification_tasks() { ->method( 'dispatch' ) ->willReturn( $this->queue_mock ); - $this->controller->queue_dispatch_resend_notification_tasks( [ 'id' => 0 ], [ 'id' => 0 ] ); + + $this->controller->queue_dispatch_resend_notification_tasks( [ 'id' => 0 ], [ 'id' => 0, 'form_id' => 0 ] ); $this->assertSame( 0, $spy->getInvocationCount() ); $this->queue_mock->push_to_queue( 'item' ); - $this->controller->queue_dispatch_resend_notification_tasks( [ 'id' => 0 ], [ 'id' => 0 ]); + $this->controller->queue_dispatch_resend_notification_tasks( [ 'id' => 0 ], [ 'id' => 0, 'form_id' => 0 ]); $this->assertSame( 1, $spy->getInvocationCount() ); } @@ -359,21 +356,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/Statics/Test_Cache.php b/tests/phpunit/unit-tests/Statics/Test_Cache.php new file mode 100644 index 000000000..a1029b360 --- /dev/null +++ b/tests/phpunit/unit-tests/Statics/Test_Cache.php @@ -0,0 +1,74 @@ +create_form_and_entries(); + + $form = $results['form']; + $entry = $results['entry']; + + $pdf_settings = $form['gfpdf_form_settings']['555ad84787d7e']; + $pdf_settings['template'] = 'zadani'; + + /* Verify the hash is the same when called multiple times with the same inputs */ + $hash1 = Cache::get_hash( $form, $entry, $pdf_settings ); + $hash2 = Cache::get_hash( $form, $entry, $pdf_settings ); + + $this->assertEquals( $hash1, $hash2 ); + + /* Verify the hash changes when the input changes */ + $pdf_settings['active'] = false; + + $hash3 = Cache::get_hash( $form, $entry, $pdf_settings ); + + $this->assertNotEquals( $hash3, $hash2 ); + } + + protected function create_form_and_entries() { + global $gfpdf; + + $form = $GLOBALS['GFPDF_Test']->form['all-form-fields']; + $entry = $GLOBALS['GFPDF_Test']->entries['all-form-fields'][0]; + + $gfpdf->data->form_settings = []; + $gfpdf->data->form_settings[ $form['id'] ] = $form['gfpdf_form_settings']; + + return [ + 'form' => $form, + 'entry' => $entry, + ]; + } + + public function test_get_path() { + $results = $this->create_form_and_entries(); + + $form = $results['form']; + $entry = $results['entry']; + + $pdf_settings = $form['gfpdf_form_settings']['555ad84787d7e']; + $pdf_settings['template'] = 'zadani'; + + $hash1 = Cache::get_hash( $form, $entry, $pdf_settings ); + $path = Cache::get_path( $form, $entry, $pdf_settings ); + + $this->assertStringEndsWith( '/' . $hash1 . '/', $path ); + $this->assertStringStartsWith( ABSPATH, $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..f33cc3d4e 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; @@ -125,6 +126,77 @@ private function create_form_and_entries() { ]; } + /** + * Test the deprecated legacy PDF endpoint is secured and will generate a PDF successfully + */ + 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' ); + + /* Test our endpoint is firing correctly */ + $results = $this->create_form_and_entries(); + + $_GET['gf_pdf'] = 1; + $_GET['fid'] = $results['form']['id']; + $_GET['lid'] = $results['entry']['id']; + $_GET['template'] = 'zadani.php'; + + /* Check middleware security is applied */ + try { + wp_set_current_user( 0 ); + $this->controller->process_legacy_pdf_endpoint(); + } catch ( Exception $e ) { + $this->assertEquals( 'Redirecting', $e->getMessage() ); + } + + /* Check pdf successfully generated */ + try { + $user_id = $this->factory->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); + $this->controller->process_legacy_pdf_endpoint(); + } catch ( Exception $e ) { + $this->assertEquals( 'The PDF cannot be displayed because the server headers have already been sent.', $e->getMessage() ); + + return; + } + + $this->fail( 'This test did not successfully complete' ); + } + + /** + * Test the DF endpoint is secured and will generate a PDF successfully + */ + public function test_process_pdf_endpoint() { + + /* Test our endpoint is firing correctly */ + $results = $this->create_form_and_entries(); + + $GLOBALS['wp']->query_vars['gpdf'] = 1; + $GLOBALS['wp']->query_vars['lid'] = $results['entry']['id']; + $GLOBALS['wp']->query_vars['pid'] = '556690c67856b'; + + /* Check middleware security is applied */ + try { + wp_set_current_user( 0 ); + $this->controller->process_pdf_endpoint(); + } catch ( Exception $e ) { + $this->assertEquals( 'Redirecting', $e->getMessage() ); + } + + /* Check pdf successfully generated */ + try { + $user_id = $this->factory->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); + $this->controller->process_pdf_endpoint(); + } catch ( Exception $e ) { + $this->assertEquals( 'The PDF cannot be displayed because the server headers have already been sent.', $e->getMessage() ); + + return; + } + + $this->fail( 'This test did not successfully complete' ); + } + /** * Test our PDF generator function works as expected * This function prepares all the details for generating a PDF and is our authentication layer @@ -155,15 +227,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 +278,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 +311,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 +490,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 ); + } } }