diff --git a/pdf.php b/pdf.php index 516ecc53a..77db9a929 100644 --- a/pdf.php +++ b/pdf.php @@ -500,7 +500,9 @@ public function notice_body_content() { * @since 6.12 * @deprecated */ - public function maybe_display_canonical_plugin_notice() {} + public function maybe_display_canonical_plugin_notice() { + _deprecated_function( __METHOD__, '6.12' ); + } /** * Notify administrator they are not using the canonical version of Gravity PDF @@ -510,7 +512,9 @@ public function maybe_display_canonical_plugin_notice() {} * @since 6.12 * @deprecated */ - public function maybe_display_canonical_plugin_notice_below_plugin( $plugin_file, $plugin_data ) {} + public function maybe_display_canonical_plugin_notice_below_plugin( $plugin_file, $plugin_data ) { + _deprecated_function( __METHOD__, '6.12' ); + } } } diff --git a/src/Controller/Controller_Actions.php b/src/Controller/Controller_Actions.php index 08434dc93..c6e5772b9 100644 --- a/src/Controller/Controller_Actions.php +++ b/src/Controller/Controller_Actions.php @@ -119,6 +119,7 @@ public function add_actions() { * condition: The function or method to call to determine if a notice should be displayed (Boolean) * process: The function to handle a successful action. On success the disable_route() method should be called * view: The function used to display the notice content + * view_class: Optional classes for the notice box, including a `notice-*` state like `notice-warning` * * @return array * @@ -158,6 +159,11 @@ public function route_notices() { return null; } + /* Don't run the route conditions, which query the database, on pages that discard the notice anyway */ + if ( ! $this->notices->can_display_notice_on_this_page() ) { + return null; + } + foreach ( $this->get_routes() as $route ) { /* Before displaying check the user has the correct capabilities, the notice isn't already been dismissed and the route condition has been met */ @@ -173,8 +179,10 @@ public function route_notices() { ] ); - $class = ( isset( $route['view_class'] ) ) ? $route['view_class'] : ''; - $this->notices->add_notice( call_user_func( $route['view'], $route['action'], $route['action_text'] ), $class ); + $this->notices->add_notice( + call_user_func( $route['view'], $route['action'], $route['action_text'] ), + $route['view_class'] ?? '' + ); } } } diff --git a/src/Controller/Controller_Install.php b/src/Controller/Controller_Install.php index 66fc0b99a..3c488a839 100644 --- a/src/Controller/Controller_Install.php +++ b/src/Controller/Controller_Install.php @@ -206,6 +206,6 @@ public function check_install_status() { * @deprecated 6.0 */ public function maybe_uninstall() { - _doing_it_wrong( __METHOD__, 'This method has been moved to Controller_Uninstall::uninstall_addon()', '6.0' ); + _deprecated_function( __METHOD__, '6.0', 'Controller_Uninstall::uninstall_addon()' ); } } diff --git a/src/Controller/Controller_PDF.php b/src/Controller/Controller_PDF.php index 2febd1fcc..22311635b 100644 --- a/src/Controller/Controller_PDF.php +++ b/src/Controller/Controller_PDF.php @@ -11,6 +11,7 @@ use GFPDF\Helper\Helper_PDF; use GFPDF\Model\Model_PDF; use GFPDF\Statics\Debug; +use GFPDF\Statics\Deprecation; use GFPDF\View\View_PDF; use GFPDF_Vendor\Psr\Log\LoggerInterface; @@ -204,7 +205,7 @@ public function add_filters() { 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 ); + add_filter( 'gfpdfe_pre_load_template', Deprecation::INTERNAL_FILTER_CALLBACK, 1, 8 ); /* Pre-process our template arguments and automatically render them in PDF */ add_filter( 'gfpdf_template_args', [ $this->model, 'preprocess_template_arguments' ] ); @@ -292,7 +293,7 @@ public function process_legacy_pdf_endpoint() { return null; } - _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' ); + _deprecated_function( __METHOD__, '4.0', 'the [gravitypdf] shortcode or PDF merge tags, https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags' ); $config = [ 'lid' => (int) explode( ',', $_GET['lid'] )[0], @@ -310,6 +311,9 @@ public function process_legacy_pdf_endpoint() { $this->pdf_error( $pid ); } + /* Record the hit so it can be surfaced in the System Report ahead of the 7.0 removal */ + Deprecation::record_legacy_endpoint_usage( $config['fid'] ); + /* Store our ids in the WP query_vars object */ $GLOBALS['wp']->query_vars['gpdf'] = 1; $GLOBALS['wp']->query_vars['pid'] = $pid; @@ -323,7 +327,7 @@ public function process_legacy_pdf_endpoint() { ] ); - $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.' ); + $this->log->warning( sprintf( 'Legacy PDF URLs are removed in Gravity PDF %s. Replace with the [gravitypdf] shortcode or PDF merge tags. See https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags for usage instructions.', Deprecation::REMOVED_IN ) ); /* Send to our model to handle validation / authentication */ do_action( 'gfpdf_legacy_pre_view_or_download_pdf', $config['lid'], $pid, $config['action'] ); @@ -399,7 +403,7 @@ protected function add_pre_view_or_download_pdf_hooks( $form, $entry, $settings * @deprecated 6.12 All buffers are auto-closed before a PDF is sent to the browser */ public function sgoptimizer_html_minification_fix() { - _doing_it_wrong( __METHOD__, 'This method has been removed and no alternative is available.', '6.12' ); + _deprecated_function( __METHOD__, '6.12' ); } /** diff --git a/src/Controller/Controller_Pdf_Queue.php b/src/Controller/Controller_Pdf_Queue.php index f9c5debcb..cf7c4227a 100644 --- a/src/Controller/Controller_Pdf_Queue.php +++ b/src/Controller/Controller_Pdf_Queue.php @@ -241,7 +241,7 @@ public function queue_async_form_submission_tasks( $entry, $form ) { */ public function queue_dispatch_resend_notification_tasks( $form = null, $entry = null ) { if ( ! is_null( $entry ) ) { - _doing_it_wrong( __METHOD__, '$entry argument ignored and now set in self::should_send_async_notification()', '6.13.5' ); + _deprecated_argument( __METHOD__, '6.13.5', 'The $entry argument is ignored and now set in self::should_send_async_notification()' ); } /* loop over all form/entries */ @@ -305,7 +305,7 @@ public function queue_async_tasks( $form, $entry ) { * @deprecated 6.12.0 Caching layer + auto-purge added */ public function queue_cleanup_task( $form, $entry ) { - _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' ); + _deprecated_function( __METHOD__, '6.12' ); } /** @@ -516,6 +516,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' ) ); + _deprecated_function( __METHOD__, '6.11' ); } } diff --git a/src/Controller/Controller_System_Report.php b/src/Controller/Controller_System_Report.php index 53923cf06..586ac3572 100644 --- a/src/Controller/Controller_System_Report.php +++ b/src/Controller/Controller_System_Report.php @@ -3,6 +3,7 @@ namespace GFPDF\Controller; use GFPDF\Helper\Helper_Abstract_Controller; +use GFPDF\Helper\Helper_Abstract_Form; use GFPDF\Helper\Helper_Abstract_Model; use GFPDF\Helper\Helper_Abstract_View; use GFPDF\Model\Model_System_Report; @@ -38,12 +39,23 @@ class Controller_System_Report extends Helper_Abstract_Controller { */ public $view; - public function __construct( Helper_Abstract_Model $model, Helper_Abstract_View $view ) { + /** + * Holds the abstracted Gravity Forms API specific to Gravity PDF + * + * @var Helper_Abstract_Form + * + * @since 6.17.0 + */ + protected $gform; + + public function __construct( Helper_Abstract_Model $model, Helper_Abstract_View $view, Helper_Abstract_Form $gform ) { $this->model = $model; $this->model->setController( $this ); $this->view = $view; $this->view->setController( $this ); + + $this->gform = $gform; } /** @@ -62,6 +74,63 @@ public function init() { */ public function add_filters() { add_filter( 'gform_system_report', [ $this, 'system_report' ] ); + add_filter( 'site_status_tests', [ $this, 'site_status_tests' ] ); + add_filter( 'debug_information', [ $this, 'debug_information' ] ); + } + + /** + * Register a Site Health test for any deprecated functionality in use on this site + * + * The admin notice can be dismissed for good, and the Gravity Forms system report has to be sought out. This + * puts the same detections where WordPress reports the rest of a site's problems, and keeps them there until + * they're fixed. + * + * @param array $tests + * + * @return array + * @since 6.17.0 + */ + public function site_status_tests( $tests ) { + if ( ! is_array( $tests ) || ! $this->gform->has_capability( 'gravityforms_view_settings' ) ) { + return $tests; + } + + $tests['direct']['gravity_pdf_deprecated_features'] = [ + 'label' => esc_html__( 'Deprecated Gravity PDF functionality', 'gravity-pdf' ), + 'test' => [ $this, 'deprecated_features_test' ], + ]; + + return $tests; + } + + /** + * Run the deprecated functionality Site Health test + * + * @return array + * @since 6.17.0 + */ + public function deprecated_features_test() { + return $this->view->get_deprecated_features_test( $this->model->get_deprecated_signals() ); + } + + /** + * Add the deprecated functionality to the Site Health Info tab + * + * The Info tab is what users copy into a support ticket, so the detections travel with it. + * + * @param array $info + * + * @return array + * @since 6.17.0 + */ + public function debug_information( $info ) { + if ( ! is_array( $info ) || ! $this->gform->has_capability( 'gravityforms_view_settings' ) ) { + return $info; + } + + $info['gravity-pdf-deprecated'] = $this->view->get_deprecated_debug_information( $this->model->get_deprecated_signals() ); + + return $info; } /** diff --git a/src/Helper/Fields/Field_Signature.php b/src/Helper/Fields/Field_Signature.php index e443aaeb0..0641111b2 100644 --- a/src/Helper/Fields/Field_Signature.php +++ b/src/Helper/Fields/Field_Signature.php @@ -4,6 +4,7 @@ use GFFormsModel; use GFPDF\Helper\Helper_Abstract_Fields; +use GFPDF\Statics\Deprecation; /** * @package Gravity PDF @@ -132,7 +133,7 @@ public function value() { * @param integer The original image width */ if ( $signature_details !== false ) { - $optimised_width = apply_filters( 'gfpdfe_signature_width', $signature_details[0] / 3, $signature_details[0] ); /* backwards compat */ + $optimised_width = Deprecation::apply_filters( 'gfpdfe_signature_width', [ $signature_details[0] / 3, $signature_details[0] ] ); /* See https://docs.gravitypdf.com/developers/filters/gfpdf_signature_width/ for more details about this filter */ $optimised_width = apply_filters( 'gfpdf_signature_width', $optimised_width, $signature_details[0] ); diff --git a/src/Helper/Helper_Abstract_View.php b/src/Helper/Helper_Abstract_View.php index e78fff80c..bac788e4e 100644 --- a/src/Helper/Helper_Abstract_View.php +++ b/src/Helper/Helper_Abstract_View.php @@ -108,6 +108,10 @@ final protected function load( $filename, $args = [], $output = true ) { $args = array_merge( $this->data_cache, $args ); + if ( isset( $args['content'] ) ) { + _deprecated_argument( esc_html( $this->view_type . '/' . $filename . '.php' ), '6.4.0', "Use \$args['callback'] instead" ); + } + if ( is_readable( $path ) ) { if ( $output ) { diff --git a/src/Helper/Helper_Misc.php b/src/Helper/Helper_Misc.php index 7662064b9..3b17db283 100644 --- a/src/Helper/Helper_Misc.php +++ b/src/Helper/Helper_Misc.php @@ -788,7 +788,7 @@ public function get_legacy_ids( $entry_id, $settings ) { * @deprecated 6.12 compatibility code no longer required */ public function maybe_add_multicurrency_support() { - _doing_it_wrong( __METHOD__, 'This method has been removed and no alternative is available.', '6.12' ); + _deprecated_function( __METHOD__, '6.12' ); } /** diff --git a/src/Helper/Helper_Notices.php b/src/Helper/Helper_Notices.php index 6c6210ebf..58180865a 100644 --- a/src/Helper/Helper_Notices.php +++ b/src/Helper/Helper_Notices.php @@ -61,7 +61,9 @@ public function add_actions(): void { * @since 6.5 * @deprecated 6.11 No longer required. Running all notices through standard WP hooks, but have included `gf-notice` class so GF does not remove it */ - public function maybe_remove_non_pdf_messages(): void {} + public function maybe_remove_non_pdf_messages(): void { + _deprecated_function( __METHOD__, '6.11' ); + } /** * Determine which notice should be triggered @@ -80,32 +82,32 @@ protected function get_notice_type(): string { * Public endpoint for adding a new notice * * @param string $notice The message to be queued - * @param string $css_class The class that should be included with the notice box + * @param string $css_class The class that should be included with the notice box. Pass a WordPress `notice-*` + * state class, like `notice-warning`, to override the default success styling. * * @since 4.0 */ public function add_notice( $notice, $css_class = '' ): void { - if ( empty( $css_class ) ) { - $this->notices[] = $notice; - } else { - $this->notices[ $css_class ] = $notice; - } + $this->notices[] = [ + 'message' => $notice, + 'class' => $css_class, + ]; } /** * Public endpoint for adding a new notice * * @param string $error The error message that should be added - * @param string $css_class Any class names that should apply to the error + * @param string $css_class Any class names that should apply to the error, including a WordPress `notice-*` + * state class to override the default error styling * * @since 4.0 */ public function add_error( $error, $css_class = '' ) { - if ( empty( $css_class ) ) { - $this->errors[] = $error; - } else { - $this->errors[ $css_class ] = $error; - } + $this->errors[] = [ + 'message' => $error, + 'class' => $css_class, + ]; } /** @@ -154,26 +156,26 @@ public function process(): void { return; } - foreach ( $this->notices as $css_class => $notice ) { - $include_class = ( ! is_int( $css_class ) ) ? $css_class : ''; - $this->html( $notice, 'updated ' . $include_class ); + foreach ( $this->notices as $notice ) { + $this->html( $notice['message'], $notice['class'], 'updated' ); } - foreach ( $this->errors as $css_class => $error ) { - $include_class = ( ! is_int( $css_class ) ) ? $css_class : ''; - $this->html( $error, 'error ' . $include_class ); + foreach ( $this->errors as $error ) { + $this->html( $error['message'], $error['class'], 'error' ); } } /** * Generate the HTML used to display the notice / error * - * @param string $text The message to be displayed - * @param string $css_class The class name (updated / error) + * @param string $text The message to be displayed + * @param string $css_class Any classes the caller asked for + * @param string $default_state The state class used when the caller hasn't chosen one (updated / error) * * @since 4.0 + * @since 6.17.0 Added $default_state */ - protected function html( string $text, string $css_class = 'updated' ): void { + protected function html( string $text, string $css_class = '', string $default_state = 'updated' ): void { $allow_form_elements = static function ( $tags ) { $tags['input'] = [ 'type' => true, @@ -187,12 +189,16 @@ protected function html( string $text, string $css_class = 'updated' ): void { add_filter( 'wp_kses_allowed_html', $allow_form_elements ); + /* `div.updated` out-specifies `.notice-warning`, so a caller-supplied state replaces ours instead of joining it */ + $state = strpos( $css_class, 'notice-' ) === false ? $default_state : ''; + $classes = trim( $state . ' ' . $css_class ); + /* Add specific classes on Gravity Forms page so the notice displays correctly */ if ( class_exists( 'GFForms' ) && \GFForms::is_gravity_page() ) { - $classes = 'notice gf-notice gform-settings__wrapper ' . $css_class; - $classes .= strpos( $css_class, 'updated' ) !== false ? ' notice-success' : ''; + $classes = 'notice gf-notice gform-settings__wrapper ' . $classes; + $classes .= $state === 'updated' ? ' notice-success' : ''; } else { - $classes = 'notice ' . $css_class; + $classes = 'notice ' . $classes; } ?> @@ -214,8 +220,9 @@ protected function html( string $text, string $css_class = 'updated' ): void { * @return bool * * @since 6.11 + * @since 6.17.0 Made public so callers can skip building a notice that would be discarded */ - protected function can_display_notice_on_this_page() { + public function can_display_notice_on_this_page() { global $pagenow; $misc = \GPDFAPI::get_misc_class(); @@ -251,6 +258,8 @@ protected function can_display_notice_on_this_page() { * @deprecated 6.11 No longer required. Running all notices through standard WP hooks, but have included `gf-notice` class so GF does not remove it */ public function reset_gravityforms_messages( $messages ) { + _deprecated_function( __METHOD__, '6.11' ); + return $messages; } @@ -265,6 +274,8 @@ public function reset_gravityforms_messages( $messages ) { * @deprecated 6.11 No longer required. Running all notices through standard WP hooks, but have included `gf-notice` class so GF does not remove it */ public function set_gravitypdf_notices( $messages ) { + _deprecated_function( __METHOD__, '6.11' ); + return $messages; } @@ -279,6 +290,8 @@ public function set_gravitypdf_notices( $messages ) { * @deprecated 6.11 No longer required. Running all notices through standard WP hooks, but have included `gf-notice` class so GF does not remove it */ public function set_gravitypdf_errors( $errors ) { + _deprecated_function( __METHOD__, '6.11' ); + return $errors; } } diff --git a/src/Helper/Helper_Options_Fields.php b/src/Helper/Helper_Options_Fields.php index 07a3eb1cf..b24d1275b 100644 --- a/src/Helper/Helper_Options_Fields.php +++ b/src/Helper/Helper_Options_Fields.php @@ -2,6 +2,7 @@ namespace GFPDF\Helper; +use GFPDF\Statics\Deprecation; use WP_Error; /** @@ -693,11 +694,11 @@ public function get_registered_fields() { */ public function get_advanced_template_field( $settings ) { - if ( ! class_exists( 'gfpdfe_business_plus' ) ) { + if ( ! Deprecation::has_tier_2_addon() ) { return $settings; } - _doing_it_wrong( __METHOD__, 'Legacy templates are deprecated and no longer supported. Contact GravityPDF.com to discuss upgrade options.', '6.12' ); + _deprecated_function( __METHOD__, '6.12', 'a Gravity PDF 6 template, https://docs.gravitypdf.com/developers/upgrade-legacy-template/' ); $settings['advanced_template'] = [ 'id' => 'advanced_template', diff --git a/src/Helper/Helper_PDF.php b/src/Helper/Helper_PDF.php index 4ae0666c4..b385a9d19 100644 --- a/src/Helper/Helper_PDF.php +++ b/src/Helper/Helper_PDF.php @@ -5,6 +5,7 @@ use Exception; use GFPDF\Helper\Mpdf\Request; use GFPDF\Statics\Cache; +use GFPDF\Statics\Deprecation; use GFPDF_Vendor\Mpdf\Config\FontVariables; use GFPDF\Helper\Mpdf\Mpdf; use GFPDF_Vendor\Mpdf\MpdfException; @@ -266,8 +267,8 @@ public function render_html( $args = [], $html = '' ) { } /* Apply our filters */ - $html = apply_filters( 'gfpdfe_pdf_template', $html, $form['id'], $this->entry['id'], $args['settings'] ); /* Backwards compat */ - $html = apply_filters( 'gfpdfe_pdf_template_' . $form['id'], $html, $this->entry['id'], $args['settings'] ); /* Backwards compat */ + $html = Deprecation::apply_filters( 'gfpdfe_pdf_template', [ $html, $form['id'], $this->entry['id'], $args['settings'] ] ); + $html = Deprecation::apply_filters( 'gfpdfe_pdf_template_' . $form['id'], [ $html, $this->entry['id'], $args['settings'] ], 'gfpdf_pdf_html_output_' . $form['id'] ); /* See https://docs.gravitypdf.com/developers/filters/gfpdf_pdf_html_output/ for more details about these filters */ $html = apply_filters( 'gfpdf_pdf_html_output', $html, $form, $this->entry, $args['settings'], $this ); @@ -302,9 +303,11 @@ public function generate() { $this->mpdf = apply_filters( 'gfpdf_mpdf_class', $this->mpdf, $form, $this->entry, $this->settings, $this ); /* deprecated backwards compatibility filters */ - $this->mpdf = apply_filters( 'gfpdfe_mpdf_class_pre_render', $this->mpdf, $this->entry['form_id'], $this->entry['id'], $this->settings, '', $this->get_filename() ); - $this->mpdf = apply_filters( 'gfpdfe_pre_render_pdf', $this->mpdf, $this->entry['form_id'], $this->entry['id'], $this->settings, '', $this->get_filename() ); - $this->mpdf = apply_filters( 'gfpdfe_mpdf_class', $this->mpdf, $this->entry['form_id'], $this->entry['id'], $this->settings, '', $this->get_filename() ); + $legacy_args = [ $this->entry['form_id'], $this->entry['id'], $this->settings, '', $this->get_filename() ]; + + $this->mpdf = Deprecation::apply_filters( 'gfpdfe_mpdf_class_pre_render', array_merge( [ $this->mpdf ], $legacy_args ) ); + $this->mpdf = Deprecation::apply_filters( 'gfpdfe_pre_render_pdf', array_merge( [ $this->mpdf ], $legacy_args ) ); + $this->mpdf = Deprecation::apply_filters( 'gfpdfe_mpdf_class', array_merge( [ $this->mpdf ], $legacy_args ) ); do_action( 'gfpdf_pre_pdf_generation_output', $this->mpdf, $form, $this->entry, $this->settings, $this ); diff --git a/src/Helper/Helper_Templates.php b/src/Helper/Helper_Templates.php index 7a86d4722..88f171139 100644 --- a/src/Helper/Helper_Templates.php +++ b/src/Helper/Helper_Templates.php @@ -3,6 +3,7 @@ namespace GFPDF\Helper; use Exception; +use GFPDF\Statics\Deprecation; use GPDFAPI; use GFPDF_Vendor\Psr\Log\LoggerInterface; use stdClass; @@ -273,6 +274,19 @@ function ( $template_path ) { ); } + /** + * Check if the template info describes a legacy (v3) template + * + * A v3 template has no `Group` header, so self::get_template_info_by_path() falls back to the Legacy group. + * + * @param array $info The template header info + * + * @since 6.17.0 + */ + public function is_legacy_template( array $info ): bool { + return ( $info['group'] ?? '' ) === esc_html__( 'Legacy', 'gravity-pdf' ); + } + /** * Get the PDF template path by the template name * @@ -766,7 +780,7 @@ public function get_template_arguments( $form, $fields, $entry, $form_data, $set 'form_id' => $form['id'], /* backwards compat */ 'lead_ids' => $legacy_ids, /* backwards compat */ - 'lead_id' => apply_filters( 'gfpdfe_lead_id', $entry['id'], $form, $entry, $gfpdf ), /* backwards compat */ + 'lead_id' => Deprecation::apply_filters( 'gfpdfe_lead_id', [ $entry['id'], $form, $entry, $gfpdf ] ), /* backwards compat */ 'form' => $form, 'entry' => $entry, diff --git a/src/Model/Model_Install.php b/src/Model/Model_Install.php index e9d82a641..ceb087b5d 100644 --- a/src/Model/Model_Install.php +++ b/src/Model/Model_Install.php @@ -10,6 +10,7 @@ use GFPDF\Helper\Helper_Misc; use GFPDF\Helper\Helper_Notices; use GFPDF\Helper\Helper_Pdf_Queue; +use GFPDF\Statics\Deprecation; use GFPDF_Vendor\Psr\Log\LoggerInterface; /** @@ -182,8 +183,8 @@ public function setup_template_location() { $upload_dir_url = $this->data->upload_dir_url; /* Legacy Filters */ - $this->data->template_location = trailingslashit( apply_filters( 'gfpdfe_template_location', $template_dir, $working_folder, $upload_dir ) ); - $this->data->template_location_url = trailingslashit( apply_filters( 'gfpdfe_template_location_uri', $template_url, $working_folder, $upload_dir_url ) ); + $this->data->template_location = trailingslashit( Deprecation::apply_filters( 'gfpdfe_template_location', [ $template_dir, $working_folder, $upload_dir ] ) ); + $this->data->template_location_url = trailingslashit( Deprecation::apply_filters( 'gfpdfe_template_location_uri', [ $template_url, $working_folder, $upload_dir_url ] ) ); /* Allow user to change directory location(s) */ @@ -398,6 +399,8 @@ public function maybe_flush_rewrite_rules( $regex ) { * @since 4.0 */ public function uninstall_plugin() { + _deprecated_function( __METHOD__, '6.0', 'Model_Uninstall::uninstall_plugin()' ); + $this->uninstall->uninstall_plugin(); } @@ -409,6 +412,8 @@ public function uninstall_plugin() { * @since 4.0 */ public function remove_plugin_options() { + _deprecated_function( __METHOD__, '6.0', 'Model_Uninstall::remove_plugin_options()' ); + $this->uninstall->remove_plugin_options(); } @@ -421,6 +426,8 @@ public function remove_plugin_options() { * @since 4.0 */ public function remove_plugin_form_settings() { + _deprecated_function( __METHOD__, '6.0', 'Model_Uninstall::remove_plugin_form_settings()' ); + $this->uninstall->remove_plugin_form_settings(); } @@ -432,6 +439,8 @@ public function remove_plugin_form_settings() { * @since 4.0 */ public function remove_folder_structure() { + _deprecated_function( __METHOD__, '6.0', 'Model_Uninstall::remove_folder_structure()' ); + $this->uninstall->remove_folder_structure(); } @@ -443,6 +452,8 @@ public function remove_folder_structure() { * @since 4.0 */ public function deactivate_plugin() { + _deprecated_function( __METHOD__, '6.0', 'Model_Uninstall::deactivate_plugin()' ); + $this->uninstall->deactivate_plugin(); } diff --git a/src/Model/Model_PDF.php b/src/Model/Model_PDF.php index 308901bd6..a040318cd 100644 --- a/src/Model/Model_PDF.php +++ b/src/Model/Model_PDF.php @@ -23,6 +23,7 @@ use GFPDF\Helper\Helper_Options_Fields; use GFPDF\Helper\Helper_PDF; use GFPDF\Helper\Helper_Templates; +use GFPDF\Statics\Deprecation; use GFPDF_Vendor\Mpdf\Mpdf; use GFPDF_Vendor\Spatie\UrlSigner\Exceptions\InvalidSignatureKey; use GFQuiz; @@ -246,7 +247,7 @@ public function process_pdf( $pid, $lid, $action = 'view' ) { * To prevent cache misses we need to ensure we don't unnecessarily modify the settings array */ unset( $settings['pdf_action'] ); - $action = apply_filters( 'gfpdfe_pdf_output_type', $action ); /* Backwards compat */ + $action = Deprecation::apply_filters( 'gfpdfe_pdf_output_type', [ $action ] ); $action = in_array( $action, [ 'view', 'download' ], true ) ? $action : 'view'; /* Get the PDF document for the request */ @@ -290,31 +291,31 @@ public function apply_backwards_compatibility_filters( $settings, $entry ) { $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); - $settings['filename'] = $this->misc->remove_extension_from_string( apply_filters( 'gfpdfe_pdf_name', $settings['filename'], $form, $entry ) ); - $settings['template'] = $this->misc->remove_extension_from_string( apply_filters( 'gfpdfe_template', $settings['template'], $form, $entry ), '.php' ); + $settings['filename'] = $this->misc->remove_extension_from_string( Deprecation::apply_filters( 'gfpdfe_pdf_name', [ $settings['filename'], $form, $entry ] ) ); + $settings['template'] = $this->misc->remove_extension_from_string( Deprecation::apply_filters( 'gfpdfe_template', [ $settings['template'], $form, $entry ] ), '.php' ); if ( isset( $settings['orientation'] ) ) { - $settings['orientation'] = apply_filters( 'gfpdf_orientation', $settings['orientation'], $form, $entry ); + $settings['orientation'] = Deprecation::apply_filters( 'gfpdf_orientation', [ $settings['orientation'], $form, $entry ] ); } if ( isset( $settings['security'] ) ) { - $settings['security'] = $this->misc->update_deprecated_config( apply_filters( 'gfpdf_security', $settings['security'], $form, $entry ) ); + $settings['security'] = $this->misc->update_deprecated_config( Deprecation::apply_filters( 'gfpdf_security', [ $settings['security'], $form, $entry ] ) ); } if ( isset( $settings['privileges'] ) ) { - $settings['privileges'] = apply_filters( 'gfpdf_privilages', $settings['privileges'], $form, $entry ); + $settings['privileges'] = Deprecation::apply_filters( 'gfpdf_privilages', [ $settings['privileges'], $form, $entry ] ); } if ( isset( $settings['password'] ) ) { - $settings['password'] = apply_filters( 'gfpdf_password', $settings['password'], $form, $entry ); + $settings['password'] = Deprecation::apply_filters( 'gfpdf_password', [ $settings['password'], $form, $entry ] ); } if ( isset( $settings['master_password'] ) ) { - $settings['master_password'] = apply_filters( 'gfpdf_master_password', $settings['master_password'], $form, $entry ); + $settings['master_password'] = Deprecation::apply_filters( 'gfpdf_master_password', [ $settings['master_password'], $form, $entry ] ); } if ( isset( $settings['rtl'] ) ) { - $settings['rtl'] = $this->misc->update_deprecated_config( apply_filters( 'gfpdf_rtl', $settings['rtl'], $form, $entry ) ); + $settings['rtl'] = $this->misc->update_deprecated_config( Deprecation::apply_filters( 'gfpdf_rtl', [ $settings['rtl'], $form, $entry ] ) ); } return $settings; @@ -865,7 +866,7 @@ public function get_pdf_name( $settings, $entry ) { $name = apply_filters( 'gfpdf_pdf_filename', $name, $form, $entry, $settings ); /* Backwards compatible filter */ - $name = apply_filters( 'gfpdfe_pdf_filename', $name, $form, $entry, $settings ); + $name = Deprecation::apply_filters( 'gfpdfe_pdf_filename', [ $name, $form, $entry, $settings ] ); /* Remove any characters that cannot be present in a filename */ $name = $this->misc->strip_invalid_characters( $name ); @@ -890,7 +891,7 @@ public function get_pdf_url( $pid, $id, $download = false, $should_print = false global $wp_rewrite; if ( $esc !== true ) { - _doing_it_wrong( __METHOD__, '$esc has been deprecated. Late-escape the returned value where appropriate.', '6.4.0' ); + _deprecated_argument( __METHOD__, '6.4.0', 'The $esc argument is ignored. Late-escape the returned value where appropriate.' ); } /* @@ -1455,17 +1456,21 @@ public function handle_legacy_tier_2_processing( Helper_PDF $pdf_generator, $ent $form = apply_filters( 'gfpdf_current_form_object', $this->gform->get_form( $entry['form_id'] ), $entry, __FUNCTION__ ); - $prevent_main_pdf_loader = apply_filters( + $this->log->warning( sprintf( 'Advanced Templating (Tier 2) processing is removed in Gravity PDF %s. Contact GravityPDF.com to discuss upgrade options.', Deprecation::REMOVED_IN ) ); + + $prevent_main_pdf_loader = Deprecation::apply_filters( 'gfpdfe_pre_load_template', - $form['id'], - $entry['id'], - basename( $pdf_generator->get_template_path() ), - $form['id'] . $entry['id'], - $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 */ + [ + $form['id'], + $entry['id'], + basename( $pdf_generator->get_template_path() ), + $form['id'] . $entry['id'], + $this->misc->backwards_compat_output( $pdf_generator->get_output_type() ), + $pdf_generator->get_filename(), + $this->misc->backwards_compat_conversion( $settings, $form, $entry ), + $args, + ] + ); return $prevent_main_pdf_loader === true; } @@ -2045,7 +2050,7 @@ public function cleanup_tmp_dir() { * @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' ); + _deprecated_function( __METHOD__, '6.12' ); /* Exit if background processing is enabled */ if ( $this->options->get_option( 'background_processing', 'No' ) === 'Yes' ) { @@ -2076,7 +2081,7 @@ public function cleanup_pdf_after_submission( $form, $entry_id ) { * @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' ); + _deprecated_function( __METHOD__, '6.12' ); $pdfs = $this->get_active_pdfs( $form['gfpdf_form_settings'] ?? [], $entry ); @@ -2112,7 +2117,7 @@ public function cleanup_pdf( $entry, $form ) { * @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' ); + _deprecated_function( __METHOD__, '6.12' ); foreach ( $entries as $entry_id ) { $entry = $this->gform->get_entry( $entry_id ); @@ -2227,7 +2232,7 @@ function ( $val ) use ( &$flattened_fonts_array ) { * @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' ); + _deprecated_function( __METHOD__, '4.0', 'the [gravitypdf] shortcode or PDF merge tags, https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags' ); /* Get the form settings */ $pdfs = $this->options->get_form_pdfs( $config['fid'] ); diff --git a/src/Model/Model_Settings.php b/src/Model/Model_Settings.php index 335b3c014..b2b391c89 100644 --- a/src/Model/Model_Settings.php +++ b/src/Model/Model_Settings.php @@ -720,7 +720,9 @@ public function deactivate_license_key( Helper_Abstract_Addon $addon, $license_k * * @deprecated Removed in 6.0. Use GPDFAPI::delete_pdf_font() */ - public function remove_font_file( $fonts ) {} + public function remove_font_file( $fonts ) { + _deprecated_function( __METHOD__, '6.0', 'GPDFAPI::delete_pdf_font()' ); + } /** * Check that the font name passed conforms to our expected naming convention @@ -731,7 +733,9 @@ public function remove_font_file( $fonts ) {} * * @deprecated Moved in 6.0. Use Model_Custom_Fonts::check_font_name_valid() */ - public function is_font_name_valid( $name ) {} + public function is_font_name_valid( $name ) { + _deprecated_function( __METHOD__, '6.0', 'Model_Custom_Fonts::check_font_name_valid()' ); + } /** * Query our custom fonts options table and check if the font name already exists @@ -743,7 +747,9 @@ public function is_font_name_valid( $name ) {} * * @deprecated Removed in 6.0. Font names no longer need to be unique */ - public function is_font_name_unique( $name, $id = '' ) {} + public function is_font_name_unique( $name, $id = '' ) { + _deprecated_function( __METHOD__, '6.0' ); + } /** * Handles the database updates required to save a new font @@ -754,7 +760,9 @@ public function is_font_name_unique( $name, $id = '' ) {} * * @deprecated Moved in 6.0 to Model_Custom_Fonts::add_font() */ - public function install_fonts( $fonts ) {} + public function install_fonts( $fonts ) { + _deprecated_function( __METHOD__, '6.0', 'Model_Custom_Fonts::add_font()' ); + } /** * AJAX Endpoint for saving the custom font @@ -763,7 +771,9 @@ public function install_fonts( $fonts ) {} * * @deprecated Moved in 6.0. Use GPDFAPI::add_pdf_font() */ - public function save_font() {} + public function save_font() { + _deprecated_function( __METHOD__, '6.0', 'GPDFAPI::add_pdf_font()' ); + } /** * AJAX Endpoint for deleting a custom font @@ -772,7 +782,9 @@ public function save_font() {} * * @deprecated Moved in 6.0. Use GPDFAPI::delete_pdf_font() */ - public function delete_font() {} + public function delete_font() { + _deprecated_function( __METHOD__, '6.0', 'GPDFAPI::delete_pdf_font()' ); + } /** * Validate user input and save as new font @@ -783,7 +795,9 @@ public function delete_font() {} * * @deprecated Removed in 6.0. Use GPDFAPI::add_pdf_font() */ - public function process_font( $font ) {} + public function process_font( $font ) { + _deprecated_function( __METHOD__, '6.0', 'GPDFAPI::add_pdf_font()' ); + } /** * Find the font unique ID from the font name @@ -794,7 +808,9 @@ public function process_font( $font ) {} * * @deprecated Removed in 6.0. Font names no longer linked to IDs. */ - public function get_font_id_by_name( $font_name ) {} + public function get_font_id_by_name( $font_name ) { + _deprecated_function( __METHOD__, '6.0' ); + } /** * Create a file in our tmp directory and check if it is publicly accessible (i.e no .htaccess protection) @@ -803,7 +819,9 @@ public function get_font_id_by_name( $font_name ) {} * * @deprecated Functionality removed in 6.0 */ - public function check_tmp_pdf_security() {} + public function check_tmp_pdf_security() { + _deprecated_function( __METHOD__, '6.0' ); + } /** * Create a file in our tmp directory and verify if it's protected from the public @@ -815,6 +833,8 @@ public function check_tmp_pdf_security() {} * @deprecated Moved in 6.0. Use Model_System_Report::test_public_tmp_directory_access() */ public function test_public_tmp_directory_access() { + _deprecated_function( __METHOD__, '6.0', 'Model_System_Report::test_public_tmp_directory_access()' ); + /** @var Model_System_Report $model_system_report */ $model_system_report = \GPDFAPI::get_mvc_class( 'Model_System_Report' ); diff --git a/src/Model/Model_Shortcodes.php b/src/Model/Model_Shortcodes.php index ecfcb8b7b..3927d9b83 100644 --- a/src/Model/Model_Shortcodes.php +++ b/src/Model/Model_Shortcodes.php @@ -47,7 +47,7 @@ class Model_Shortcodes extends Helper_Abstract_Pdf_Shortcode { * @internal Deprecated in 5.2. Use Model_Shortcodes::process() */ public function gravitypdf( $attributes ) { - _doing_it_wrong( __METHOD__, 'This method has been replaced by Model_Shortcodes::process()', '5.2' ); + _deprecated_function( __METHOD__, '5.2', 'Model_Shortcodes::process()' ); return $this->process( $attributes ); } diff --git a/src/Model/Model_System_Report.php b/src/Model/Model_System_Report.php index 826478c9a..5bcede73c 100644 --- a/src/Model/Model_System_Report.php +++ b/src/Model/Model_System_Report.php @@ -9,6 +9,7 @@ use GFPDF\Helper\Helper_Data; use GFPDF\Helper\Helper_Misc; use GFPDF\Helper\Helper_Templates; +use GFPDF\Statics\Deprecation; use GFPDF_Major_Compatibility_Checks; use GFPDF_Vendor\Psr\Log\LoggerInterface; @@ -96,6 +97,14 @@ public function build_gravitypdf_report(): array { } } + /* Drop any section with nothing to show, which is how Deprecated Features stays hidden on a clean site */ + $structure[0]['tables'] = array_filter( + $structure[0]['tables'], + static function ( $table ) { + return ! empty( $table['items'] ); + } + ); + return $structure; } @@ -134,6 +143,12 @@ public function get_report_structure(): array { 'title_export' => $title_export_prefix . 'Security Settings', 'items' => [], ], + + [ + 'title' => esc_html__( 'Deprecated Features', 'gravity-pdf' ), + 'title_export' => $title_export_prefix . 'Deprecated Features', + 'items' => [], + ], ], ], ]; @@ -246,7 +261,7 @@ protected function get_report_items(): array { ], ]; - /* Check if outdated core template overrides and display warning */ + /* Check for outdated core template overrides and display a warning */ $template_status = $this->check_core_template_override_versions(); if ( ! empty( $template_status ) ) { $items[1]['outdated_templates'] = [ @@ -315,9 +330,56 @@ protected function get_report_items(): array { ], ]; + /* Deprecated Features */ + $items[] = $this->get_deprecated_feature_items(); + return apply_filters( 'gfpdf_system_status_report_items', $items ); } + /** + * Build the rows for the Deprecated Features section + * + * Each row is only included when that feature is actually in use on this site, and the section itself is + * discarded by build_gravitypdf_report() when nothing is detected. + * + * @since 6.17.0 + */ + protected function get_deprecated_feature_items(): array { + $view = $this->getController()->view; + $labels = Deprecation::get_feature_labels(); + + $items = []; + foreach ( $this->get_deprecated_signals() as $key => $signal ) { + $items[ $key ] = [ + 'label' => esc_html( $labels[ $key ] ), + 'label_export' => $labels[ $key ], + ] + $view->get_deprecated_feature_message( $key, $signal ); + } + + return $items; + } + + /** + * Get the deprecated functionality in use on this site, ready to display + * + * @since 6.17.0 + */ + public function get_deprecated_signals(): array { + $signals = Deprecation::get_signals( true ); + + /* Show template paths relative to the WordPress root, as the rest of the report does */ + if ( isset( $signals['legacy_templates'] ) ) { + $signals['legacy_templates'] = array_map( + function ( $path ) { + return $this->misc->relative_path( $path, '/' ); + }, + $signals['legacy_templates'] + ); + } + + return $signals; + } + /** * Returns text and dashicon for Memory Limit * @@ -446,7 +508,7 @@ protected function check_core_template_override_versions(): array { foreach ( $templates as $path => $core_version ) { $template = $this->templates->get_template_info_by_id( basename( $path, '.php' ) ); if ( version_compare( $core_version, $template['version'], '>' ) ) { - $relative_template_path = str_replace( ABSPATH, '/', $template['path'] ); + $relative_template_path = $this->misc->relative_path( $template['path'], '/' ); $message = $this->getController()->view->get_template_check_message( $relative_template_path, $template['version'], $core_version ); $value .= $message['value']; diff --git a/src/Model/Model_Templates.php b/src/Model/Model_Templates.php index 3c84324b1..f0cbd5635 100644 --- a/src/Model/Model_Templates.php +++ b/src/Model/Model_Templates.php @@ -391,7 +391,7 @@ public function check_for_valid_pdf_templates( $files = [] ) { /* Check if we have a valid v4 template header in the file */ $info = $this->templates->get_template_info_by_path( $file ); - if ( $info['group'] === esc_html__( 'Legacy', 'gravity-pdf' ) ) { + if ( $this->templates->is_legacy_template( $info ) ) { /* Check if it's a v3 template */ $fp = fopen( $file, 'rb' ); $file_data = fread( $fp, 8192 ); diff --git a/src/Statics/Deprecation.php b/src/Statics/Deprecation.php new file mode 100644 index 000000000..f103d47cc --- /dev/null +++ b/src/Statics/Deprecation.php @@ -0,0 +1,418 @@ + + * @since 6.17.0 + */ + public static function get_deprecated_filters(): array { + return [ + 'gfpdfe_template_location' => 'gfpdf_template_location', + 'gfpdfe_template_location_uri' => 'gfpdf_template_location_uri', + 'gfpdfe_pdf_output_type' => '', + 'gfpdfe_pdf_name' => 'gfpdf_pdf_config', + 'gfpdfe_template' => 'gfpdf_pdf_config', + 'gfpdfe_pdf_filename' => 'gfpdf_pdf_filename', + 'gfpdfe_pdf_template' => 'gfpdf_pdf_html_output', + 'gfpdfe_mpdf_class_pre_render' => 'gfpdf_mpdf_class', + 'gfpdfe_pre_render_pdf' => 'gfpdf_mpdf_class', + 'gfpdfe_mpdf_class' => 'gfpdf_mpdf_class', + 'gfpdfe_lead_id' => 'gfpdf_template_args', + 'gfpdfe_signature_width' => 'gfpdf_signature_width', + 'gfpdfe_pre_load_template' => '', + 'gfpdf_orientation' => 'gfpdf_pdf_config', + 'gfpdf_security' => 'gfpdf_pdf_config', + 'gfpdf_privilages' => 'gfpdf_pdf_config', + 'gfpdf_password' => 'gfpdf_pdf_config', + 'gfpdf_master_password' => 'gfpdf_pdf_config', + 'gfpdf_rtl' => 'gfpdf_pdf_config', + ]; + } + + /** + * The signals returned by self::get_signals(), mapped to how they are named to the user + * + * @return array + * @since 6.17.0 + */ + public static function get_feature_labels(): array { + return [ + 'legacy_templates' => __( 'Legacy Templates', 'gravity-pdf' ), + 'legacy_endpoint' => __( 'Legacy PDF URLs', 'gravity-pdf' ), + 'deprecated_filters' => __( 'Deprecated Filters', 'gravity-pdf' ), + ]; + } + + /** + * Fire one of the deprecated v3 filters, warning any third party still listening to it + * + * The notice is only emitted when a listener is actually attached, so sites that have already moved on stay + * quiet. + * + * @param string $hook_name The deprecated filter to fire + * @param array $args The arguments to pass to the filter, the first of which is returned + * @param string $replacement Override the mapped replacement, for dynamic hooks + * + * @return mixed + * @since 6.17.0 + */ + public static function apply_filters( string $hook_name, array $args, string $replacement = '' ) { + /* Sidestep building the deprecation message on the vast majority of sites, which have no listener */ + if ( ! has_filter( $hook_name ) ) { + return $args[0]; + } + + if ( $replacement === '' ) { + $replacement = static::get_deprecated_filters()[ $hook_name ] ?? ''; + } + + static::log_deprecated_filter( $hook_name, $replacement ); + + return apply_filters_deprecated( + $hook_name, + $args, + '4.0', + $replacement, + sprintf( + /* translators: %s: The Gravity PDF version the filter is removed in */ + esc_html__( 'This filter is removed in Gravity PDF %s.', 'gravity-pdf' ), + static::REMOVED_IN + ) + ); + } + + /** + * Record a deprecated filter with a third-party listener in the Gravity PDF log + * + * Written once per hook per request so a PDF that fires the same filter for every field doesn't flood the log. + * + * @param string $hook_name The deprecated filter being fired + * @param string $replacement The filter to use instead, if one exists + * + * @since 6.17.0 + */ + protected static function log_deprecated_filter( string $hook_name, string $replacement ): void { + static $logged = []; + + /* `gfpdfe_template_location` fires while the container is still being built, so the logger may not exist yet */ + $log = \GPDFAPI::get_log_class(); + if ( $log === null || isset( $logged[ $hook_name ] ) ) { + return; + } + + $logged[ $hook_name ] = true; + + $log->warning( + sprintf( + 'The %1$s filter has a third-party listener attached and is removed in Gravity PDF %2$s.%3$s', + $hook_name, + static::REMOVED_IN, + $replacement !== '' ? sprintf( ' Use the %s filter instead.', $replacement ) : '' + ) + ); + } + + /** + * Record a hit on the legacy `?gf_pdf=` PDF endpoint + * + * Writes are throttled to one an hour, unless the request is for a form we haven't seen before. + * + * @param int $form_id The form the legacy URL requested a PDF for + * + * @since 6.17.0 + */ + public static function record_legacy_endpoint_usage( int $form_id ): void { + $usage = static::get_legacy_endpoint_usage(); + $form_ids = $usage['form_ids'] ?? []; + + $is_new = ! in_array( $form_id, $form_ids, true ) && count( $form_ids ) < static::MAX_RECORDED_FORMS; + $throttled = ( time() - (int) ( $usage['last_used'] ?? 0 ) ) < HOUR_IN_SECONDS; + + if ( ! $is_new && $throttled ) { + return; + } + + if ( $is_new ) { + $form_ids[] = $form_id; + sort( $form_ids ); + } + + update_option( + static::ENDPOINT_USAGE_OPTION, + [ + 'last_used' => time(), + 'form_ids' => $form_ids, + ], + false + ); + } + + /** + * Get the recorded legacy `?gf_pdf=` PDF endpoint usage + * + * @return array Empty when the endpoint has never been hit + * @since 6.17.0 + */ + public static function get_legacy_endpoint_usage(): array { + $usage = get_option( static::ENDPOINT_USAGE_OPTION, [] ); + + return is_array( $usage ) ? $usage : []; + } + + /** + * Check if the v3 "Advanced Templating" (Tier 2) add-on is installed + * + * @since 6.17.0 + */ + public static function has_tier_2_addon(): bool { + return class_exists( 'gfpdfe_business_plus' ); + } + + /** + * Check if a PDF is configured to use the v3 Advanced Templating mode + * + * Compared case-insensitively, which is how the render path has always read the setting. + * + * @param array $settings The PDF settings + * + * @since 6.17.0 + */ + public static function is_advanced_template_pdf( array $settings ): bool { + return strtolower( (string) ( $settings['advanced_template'] ?? '' ) ) === 'yes'; + } + + /** + * Get the templates any PDF still renders through the v3 Advanced Templating mode + * + * Advanced Templating hands the template full control of the PDF, which only a v3-era template does, so the + * setting identifies a legacy template even when the file itself carries a `Group` header. + * + * @return array List of absolute template paths + * @since 6.17.0 + */ + public static function get_advanced_template_paths(): array { + global $wpdb; + + /* Narrow to the forms that mention the setting at all, so sites with a lot of forms don't load every one. + Trashed forms are skipped: the user can't see them in their form list, so there's nothing to act on */ + $meta_table = \GFFormsModel::get_meta_table_name(); + $form_table = \GFFormsModel::get_form_table_name(); + + //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- the table names come from Gravity Forms + $form_ids = $wpdb->get_col( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + "SELECT meta.form_id FROM {$meta_table} meta + INNER JOIN {$form_table} form ON form.id = meta.form_id + WHERE form.is_trash = 0 AND meta.display_meta LIKE %s", + '%' . $wpdb->esc_like( 'advanced_template' ) . '%' + ) + ); + //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + $gform = \GPDFAPI::get_form_class(); + $templates = \GPDFAPI::get_templates_class(); + $paths = []; + + foreach ( $form_ids as $form_id ) { + $form = $gform->get_form( (int) $form_id ); + + foreach ( $form['gfpdf_form_settings'] ?? [] as $pdf ) { + if ( ! static::is_advanced_template_pdf( $pdf ) ) { + continue; + } + + try { + $paths[] = $templates->get_template_path_by_id( $pdf['template'] ?? '' ); + } catch ( \Exception $e ) { + /* The PDF points at a template that no longer exists, so there's nothing to upgrade */ + continue; + } + } + } + + return $paths; + } + + /** + * Get any installed legacy (v3) PDF templates + * + * A template is classified as legacy when it has no `Group` header, which is how Helper_Templates groups + * v3-era templates that predate the v4 header format, or when a PDF renders it through Advanced Templating. + * + * @return array List of absolute template paths + * @since 6.17.0 + */ + public static function get_legacy_templates(): array { + $templates = \GPDFAPI::get_templates_class(); + + $legacy = []; + foreach ( $templates->get_all_template_info() as $template ) { + if ( $templates->is_legacy_template( $template ) ) { + $legacy[] = $template['path']; + } + } + + return array_values( array_unique( array_merge( $legacy, static::get_advanced_template_paths() ) ) ); + } + + /** + * Get the deprecated v3 filters that currently have a third-party listener attached + * + * @param bool $scan_all Walk every registered hook, so dynamic filters like `gfpdfe_pdf_template_{form_id}` are + * included. Off by default because `$wp_filter` holds thousands of entries on a real site, + * and only the System Report displays the result. + * + * @return array Hook name mapped to the number of listeners + * @since 6.17.0 + */ + public static function get_active_deprecated_filters( bool $scan_all = false ): array { + global $wp_filter; + + $known = static::get_deprecated_filters(); + $hooks = (array) $wp_filter; + + /* Look the known hooks up by name, rather than filtering `$wp_filter`, which would walk all of it */ + if ( ! $scan_all ) { + $hooks = []; + + foreach ( array_keys( $known ) as $name ) { + if ( isset( $wp_filter[ $name ] ) ) { + $hooks[ $name ] = $wp_filter[ $name ]; + } + } + } + + $active = []; + foreach ( $hooks as $name => $hook ) { + if ( strpos( (string) $name, 'gfpdfe_' ) !== 0 && ! isset( $known[ $name ] ) ) { + continue; + } + + $count = static::count_third_party_callbacks( $hook ); + if ( $count > 0 ) { + $active[ $name ] = $count; + } + } + + ksort( $active ); + + return $active; + } + + /** + * Count the callbacks on a hook, ignoring the one Gravity PDF registers itself + * + * @param \WP_Hook $hook + * + * @since 6.17.0 + */ + protected static function count_third_party_callbacks( $hook ): int { + $count = 0; + + foreach ( $hook->callbacks ?? [] as $callbacks ) { + foreach ( $callbacks as $callback ) { + if ( ( $callback['function'] ?? null ) === static::INTERNAL_FILTER_CALLBACK ) { + continue; + } + + ++$count; + } + } + + return $count; + } + + /** + * Get every signal that the v3 backwards compatibility layer is in use on this site + * + * Signals with nothing to report are omitted, so the return value doubles as the list of features in use. The + * template scan is cached for a day; the System Report always refreshes it, and takes the deeper filter scan + * with it. + * + * @param bool $refresh Bypass the cache and re-run the scans + * + * @since 6.17.0 + */ + public static function get_signals( bool $refresh = false ): array { + $legacy_templates = $refresh ? false : get_transient( static::SIGNAL_CACHE_KEY ); + + if ( ! is_array( $legacy_templates ) ) { + $legacy_templates = static::get_legacy_templates(); + + set_transient( static::SIGNAL_CACHE_KEY, $legacy_templates, DAY_IN_SECONDS ); + } + + return array_filter( + [ + 'legacy_templates' => $legacy_templates, + 'legacy_endpoint' => static::get_legacy_endpoint_usage(), + 'deprecated_filters' => static::get_active_deprecated_filters( $refresh ), + ] + ); + } +} diff --git a/src/Statics/Queue_Callbacks.php b/src/Statics/Queue_Callbacks.php index e80c67d96..8bedec8b8 100644 --- a/src/Statics/Queue_Callbacks.php +++ b/src/Statics/Queue_Callbacks.php @@ -127,7 +127,7 @@ public static function send_notification( $form_id, $entry_id, $notification, $u * @deprecated 6.12 Caching layer + auto-purge added */ public static function cleanup_pdfs( $form_id, $entry_id ) { - _doing_it_wrong( __METHOD__, 'This method is deprecated and no alternative is available. The temporary cache is automatically cleaned every hour using the WP Cron.', '6.12' ); + _deprecated_function( __METHOD__, '6.12' ); $gform = GPDFAPI::get_form_class(); $log = GPDFAPI::get_log_class(); diff --git a/src/View/View_PDF.php b/src/View/View_PDF.php index e4eb90818..343f29435 100644 --- a/src/View/View_PDF.php +++ b/src/View/View_PDF.php @@ -20,6 +20,7 @@ use GFPDF\Helper\Helper_PDF; use GFPDF\Helper\Helper_Templates; use GFPDF\Statics\Debug; +use GFPDF\Statics\Deprecation; use GFPDF\Statics\Kses; use GFPDFEntryDetail; use GFPDF_Vendor\Psr\Log\LoggerInterface; @@ -151,7 +152,7 @@ public function __construct( array $data_cache, Helper_Abstract_Form $gform, Log * @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' ); + _deprecated_function( __METHOD__, '6.12', '\GPDFAPI::create_pdf()' ); $controller = $this->getController(); $model = $controller->model; @@ -159,7 +160,7 @@ public function generate_pdf( $entry, $settings ) { 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 */ + $settings['pdf_action'] = Deprecation::apply_filters( 'gfpdfe_pdf_output_type', [ $settings['pdf_action'] ?? 'download' ] ); /* Setup the PDF that will be generated */ $pdf_generator = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates, $this->log ); @@ -256,7 +257,7 @@ public function generate_pdf( $entry, $settings ) { * @deprecated 4.1 */ public function get_template_filename( $name ) { - _doing_it_wrong( __METHOD__, 'This method has been replaced by Helper_Misc::get_file_with_extension().', '4.1' ); + _deprecated_function( __METHOD__, '4.1', 'Helper_Misc::get_file_with_extension()' ); return $this->misc->get_file_with_extension( $name, '.php' ); } @@ -496,6 +497,7 @@ public function show_form_title( $show_title, $form ) { * @deprecated 6.10.1 Page fields are handled like all other fields, with markup generated using a dedicated Field_Page class */ public function display_page_name( $page, $form, Helper_Field_Container $container ) { + _deprecated_function( __METHOD__, '6.10.1', 'GFPDF\Helper\Fields\Field_Page' ); /* Only display the current page name if it exists */ if ( isset( $form['pagination']['pages'][ $page ] ) && strlen( trim( $form['pagination']['pages'][ $page ] ) ) > 0 ) { @@ -540,13 +542,24 @@ public function display_page_name( $page, $form, Helper_Field_Container $contain * @since 4.0 */ public function autoprocess_core_template_options( $html, $form, $entry, $settings ) { - /* Prevent core styles loading if a v3 template or using our legacy Tier 2 add-on */ $template_info = $this->templates->get_template_info_by_id( $settings['template'] ); - if ( - ( esc_html__( 'Legacy', 'gravity-pdf' ) !== $template_info['group'] ) && - ( empty( $settings['advanced_template'] ) || 'Yes' !== $settings['advanced_template'] ) - ) { - $html = $this->get_core_template_styles( $settings, $entry ) . $html; + $is_legacy = $this->templates->is_legacy_template( $template_info ); + $is_tier_2 = Deprecation::is_advanced_template_pdf( $settings ); + + /* Prevent core styles loading if a v3 template or using our legacy Tier 2 add-on */ + if ( ! $is_legacy && ! $is_tier_2 ) { + return $this->get_core_template_styles( $settings, $entry ) . $html; + } + + /* Model_PDF::handle_legacy_tier_2_processing() warns about the Advanced Templating half of this */ + if ( $is_legacy ) { + $this->log->warning( + sprintf( 'This PDF uses a legacy (v3) template, which is removed in Gravity PDF %s. See https://docs.gravitypdf.com/developers/upgrade-legacy-template/ for upgrade instructions.', Deprecation::REMOVED_IN ), + [ + 'template' => $settings['template'], + 'pdf_id' => $settings['id'] ?? '', + ] + ); } return $html; diff --git a/src/View/View_Settings.php b/src/View/View_Settings.php index a0b121a01..47d09a826 100644 --- a/src/View/View_Settings.php +++ b/src/View/View_Settings.php @@ -137,6 +137,8 @@ public function __construct( array $data_cache, Helper_Abstract_Form $gform, Log * @deprecated 6.4 */ public function tabs() { + _deprecated_function( __METHOD__, '6.4', 'View_Settings::sub_menu()' ); + ob_start(); $this->sub_menu(); diff --git a/src/View/View_System_Report.php b/src/View/View_System_Report.php index 03aeeb722..aed0b80d4 100644 --- a/src/View/View_System_Report.php +++ b/src/View/View_System_Report.php @@ -5,6 +5,7 @@ namespace GFPDF\View; use GFPDF\Helper\Helper_Abstract_View; +use GFPDF\Statics\Deprecation; /** * @package Gravity PDF @@ -146,6 +147,230 @@ public function get_template_check_message( string $path, string $template_versi public function get_template_upgrade_message(): string { $learn_more_url = 'https://docs.gravitypdf.com/developers/template-hierarchy'; - return $this->markup_warning . ' ' . esc_html__( 'Learn how to update', 'gravity-pdf' ) . ''; + return $this->markup_warning . ' ' . esc_html__( 'Learn how to update', 'gravity-pdf' ) . ''; + } + + /** + * Describe one of the deprecated features detected on this site + * + * Shared by the Gravity Forms system report and the Site Health test, which present the same detections + * differently. + * + * @param string $key The signal key from Deprecation::get_signals() + * @param array $signal The detections recorded for that signal + * + * @return array One `lines` entry per detection, plus the `advice` to act on and the `url` explaining it. The + * text is unescaped, as one of the three consumers is the Site Health Info tab, which escapes + * what it's given. + * @since 6.17.0 + */ + public function get_deprecated_feature( string $key, array $signal ): array { + $methods = [ + 'legacy_templates' => 'get_legacy_templates_feature', + 'legacy_endpoint' => 'get_legacy_endpoint_feature', + 'deprecated_filters' => 'get_deprecated_filter_feature', + ]; + + return $this->{$methods[ $key ]}( $signal ); + } + + /** + * Describe any installed legacy (v3) template file(s) + * + * @param array $paths The paths to the legacy PDF template files + * + * @since 6.17.0 + */ + protected function get_legacy_templates_feature( array $paths ): array { + $lines = []; + + foreach ( $paths as $path ) { + /* translators: %s: Template file path */ + $lines[] = sprintf( __( '%s is a legacy template and is no longer supported', 'gravity-pdf' ), $path ); + } + + return [ + 'lines' => $lines, + 'advice' => __( 'Learn how to upgrade legacy templates', 'gravity-pdf' ), + 'url' => 'https://docs.gravitypdf.com/developers/upgrade-legacy-template/', + ]; + } + + /** + * Describe recorded use of the legacy `?gf_pdf=` PDF endpoint + * + * @param array $usage The recorded usage, containing `last_used` and `form_ids` keys + * + * @since 6.17.0 + */ + protected function get_legacy_endpoint_feature( array $usage ): array { + $last_used = wp_date( get_option( 'date_format' ), (int) $usage['last_used'] ); + $form_ids = implode( ', ', array_map( 'intval', $usage['form_ids'] ) ); + + return [ + /* translators: 1: Date the legacy URL was last requested, 2: Comma separated list of form IDs */ + 'lines' => [ sprintf( __( 'Legacy PDF URLs were last requested on %1$s for form(s) %2$s', 'gravity-pdf' ), $last_used, $form_ids ) ], + 'advice' => __( 'Replace these links with the [gravitypdf] shortcode or PDF merge tags', 'gravity-pdf' ), + 'url' => 'https://docs.gravitypdf.com/v6/users/shortcodes-and-mergetags', + ]; + } + + /** + * Describe the deprecated v3 filters that have a third-party listener attached + * + * @param array $filters Hook name mapped to the number of listeners + * + * @since 6.17.0 + */ + protected function get_deprecated_filter_feature( array $filters ): array { + $lines = []; + + foreach ( $filters as $name => $count ) { + /* translators: 1: Filter name, 2: Number of callbacks attached to it */ + $lines[] = sprintf( __( '%1$s has %2$d listener(s)', 'gravity-pdf' ), $name, $count ); + } + + return [ + 'lines' => $lines, + 'advice' => __( 'Move these listeners to their Gravity PDF 6 equivalent', 'gravity-pdf' ), + 'url' => 'https://docs.gravitypdf.com/developers/filters/', + ]; + } + + /** + * Assemble the display and export values for a Deprecated Features row + * + * @param string $key The signal key from Deprecation::get_signals() + * @param array $signal The detections recorded for that signal + * + * @since 6.17.0 + */ + public function get_deprecated_feature_message( string $key, array $signal ): array { + $feature = $this->get_deprecated_feature( $key, $signal ); + $value = ''; + $value_export = ''; + + foreach ( $feature['lines'] as $line ) { + $value .= esc_html( $line ) . $this->get_icon( false ) . '
'; + $value_export .= $line . " ✘\n"; + } + + return [ + 'value' => $value . $this->get_removal_message( $feature['advice'], $feature['url'] ), + 'value_export' => $value_export, + ]; + } + + /** + * Build the Site Health test result for the deprecated features detected on this site + * + * Unlike the one-time admin notice, this surface can't be dismissed: it clears itself once the site stops + * using the v3 layer, and returns if it starts again. + * + * @param array $signals The signals from Deprecation::get_signals() + * + * @since 6.17.0 + */ + public function get_deprecated_features_test( array $signals ): array { + $result = [ + 'label' => esc_html__( 'Your site does not use deprecated Gravity PDF functionality', 'gravity-pdf' ), + 'status' => 'good', + 'badge' => [ + 'label' => esc_html__( 'Gravity PDF', 'gravity-pdf' ), + 'color' => 'blue', + ], + 'description' => '

' . sprintf( + /* translators: %s: The Gravity PDF version the functionality is removed in */ + esc_html__( 'Gravity PDF %s removes functionality carried over from version 3. Nothing on this site relies on it.', 'gravity-pdf' ), + esc_html( Deprecation::REMOVED_IN ) + ) . '

', + 'actions' => '', + 'test' => 'gravity_pdf_deprecated_features', + ]; + + if ( $signals === [] ) { + return $result; + } + + $labels = Deprecation::get_feature_labels(); + + $result['status'] = 'recommended'; + $result['label'] = sprintf( + /* translators: %s: The Gravity PDF version the functionality is removed in */ + esc_html__( 'Your site uses Gravity PDF functionality that is removed in version %s', 'gravity-pdf' ), + esc_html( Deprecation::REMOVED_IN ) + ); + + $result['description'] = '

' . sprintf( + /* translators: %s: The Gravity PDF version the functionality is removed in */ + esc_html__( 'The following functionality dates back to Gravity PDF version 3 and is removed in version %s. Update it before then, or the PDFs that rely on it will stop working.', 'gravity-pdf' ), + esc_html( Deprecation::REMOVED_IN ) + ) . '

'; + + foreach ( $signals as $key => $signal ) { + $feature = $this->get_deprecated_feature( $key, $signal ); + + $result['description'] .= '

' . esc_html( $labels[ $key ] ) . '

'; + $result['description'] .= ''; + $result['description'] .= '

' . esc_html( $feature['advice'] ) . '

'; + } + + $result['actions'] = '

' . esc_html__( 'View the Gravity Forms system report', 'gravity-pdf' ) . '

'; + + return $result; + } + + /** + * Prepare the trailing advice shown under each deprecated feature + * + * @param string $advice The action the user should take + * @param string $url Where to read more about that action + * + * @since 6.17.0 + */ + protected function get_removal_message( string $advice, string $url ): string { + /* translators: %s: The Gravity PDF version the feature is removed in */ + $removal = sprintf( esc_html__( 'Removed in Gravity PDF %s.', 'gravity-pdf' ), Deprecation::REMOVED_IN ); + + return $this->markup_warning . ' ' . $removal . ' ' . esc_html( $advice ) . ''; + } + + /** + * Build the Site Health Info tab section for the deprecated features detected on this site + * + * This is the tab users copy into a support ticket, so the section is always present: when nothing is + * detected it says so, rather than leaving the reader to guess whether the check ran. + * + * @param array $signals The signals from Deprecation::get_signals() + * + * @since 6.17.0 + */ + public function get_deprecated_debug_information( array $signals ): array { + $labels = Deprecation::get_feature_labels(); + $fields = []; + + foreach ( $signals as $key => $signal ) { + $fields[ $key ] = [ + 'label' => $labels[ $key ], + 'value' => implode( '; ', $this->get_deprecated_feature( $key, $signal )['lines'] ), + ]; + } + + if ( $fields === [] ) { + $fields['deprecated_features'] = [ + 'label' => __( 'Deprecated functionality', 'gravity-pdf' ), + 'value' => __( 'None detected', 'gravity-pdf' ), + ]; + } + + return [ + 'label' => __( 'Gravity PDF - Deprecated Functionality', 'gravity-pdf' ), + 'description' => sprintf( + /* translators: %s: The Gravity PDF version the functionality is removed in */ + esc_html__( 'Functionality carried over from Gravity PDF version 3, which is removed in version %s.', 'gravity-pdf' ), + esc_html( Deprecation::REMOVED_IN ) + ), + 'fields' => $fields, + ]; } } diff --git a/src/bootstrap.php b/src/bootstrap.php index 9f5a75c6e..c0a694a07 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -130,8 +130,7 @@ class Router implements Helper\Helper_Interface_Actions, Helper\Helper_Interface * @since 4.0 */ public function __call( $name, $arguments ) { - /* translators: %s: deprecated method name */ - _doing_it_wrong( esc_html( $name ), esc_html( sprintf( __( '"%s" has been deprecated as of Gravity PDF 4.0', 'gravity-pdf' ), $name ) ), '4.0' ); + _deprecated_function( esc_html( $name ), '4.0' ); } /** @@ -143,7 +142,7 @@ public function __call( $name, $arguments ) { * @since 4.0 */ public static function __callStatic( $name, $arguments ) { - _doing_it_wrong( esc_html( $name ), esc_html( sprintf( __( '"%s" has been deprecated as of Gravity PDF 4.0', 'gravity-pdf' ), $name ) ), '4.0' ); + _deprecated_function( esc_html( $name ), '4.0' ); } /** @@ -920,7 +919,7 @@ public function load_debug() { public function check_system_status() { $view = new View\View_System_Report(); $model = new Model\Model_System_Report( $this->options, $this->data, $this->log, $this->misc, new GFPDF_Major_Compatibility_Checks(), $this->templates ); - $class = new Controller\Controller_System_Report( $model, $view ); + $class = new Controller\Controller_System_Report( $model, $view, $this->gform ); $class->init(); $this->singleton->add_class( $class ); diff --git a/src/deprecated.php b/src/deprecated.php index 202c6817f..fca9b949b 100644 --- a/src/deprecated.php +++ b/src/deprecated.php @@ -42,7 +42,7 @@ abstract class GFPDF_Deprecated_Abstract { * */ public function __call( $name, $arguments ) { - _doing_it_wrong( esc_html( $name ), esc_html( sprintf( __( '"%s" has been deprecated as of Gravity PDF 4.0', 'gravity-pdf' ), $name ) ), '4.0' ); + _deprecated_function( esc_html( static::class . '::' . $name ), '4.0' ); } /** @@ -55,7 +55,7 @@ public function __call( $name, $arguments ) { * */ public static function __callStatic( $name, $arguments ) { - _doing_it_wrong( esc_html( $name ), esc_html( sprintf( __( '"%s" has been deprecated as of Gravity PDF 4.0', 'gravity-pdf' ), $name ) ), '4.0' ); + _deprecated_function( esc_html( static::class . '::' . $name ), '4.0' ); } } @@ -130,6 +130,7 @@ class PDFRender extends GFPDF_Deprecated_Abstract { * @since 3.0 */ public function savePDF( $raw_pdf_string, $filename, $id ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::create_pdf()' ); /* create our path */ $path = apply_filters( 'gfpdf_legacy_save_path', PDF_SAVE_LOCATION . $id . '/', $filename, $id ); @@ -166,6 +167,7 @@ public function savePDF( $raw_pdf_string, $filename, $id ) { * */ public static function prepare_ids( $form_id, $lead_id, $template, $id, $output, $filename, $arguments, $args ) { + /* No deprecation notice — core registers this callback itself on `gfpdfe_pre_load_template` */ global $lead_ids; $lead_ids = $args['lead_ids']; @@ -188,6 +190,8 @@ class PDF_Common extends GFPDF_Deprecated_Abstract { * @since 4.0 */ public static function get_ids() { + _deprecated_function( __METHOD__, '4.0' ); + global $form_id, $lead_id, $lead_ids; $form_id = ! empty( (int) $form_id ) ? (int) $form_id : (int) rgget( 'fid' ); @@ -209,6 +213,8 @@ public static function get_ids() { * @since 3.0 */ public static function get_upload_dir() { + _deprecated_function( __METHOD__, '4.0', 'Helper_Misc::get_upload_details()' ); + $misc = GPDFAPI::get_misc_class(); return $misc->get_upload_details(); @@ -226,6 +232,8 @@ public static function get_upload_dir() { * @since 3.0 */ public static function do_mergetags( $text, $form_id, $lead_id ) { + _deprecated_function( __METHOD__, '4.0', 'Helper_Form::process_tags()' ); + $gform = GPDFAPI::get_form_class(); return $gform->process_tags( $text, $gform->get_form( $form_id ), $gform->get_entry( $lead_id ) ); @@ -239,6 +247,8 @@ public static function do_mergetags( $text, $form_id, $lead_id ) { * @since 4.0 */ public static function view_data( $form_data ) { + _deprecated_function( __METHOD__, '4.0' ); + $pdf_view = \GPDFAPI::get_pdf_class(); $pdf_view->maybe_view_form_data( $form_data ); } @@ -253,6 +263,8 @@ public static function view_data( $form_data ) { * @since 3.0 */ public static function post( $name ) { + _deprecated_function( __METHOD__, '4.0' ); + /* phpcs:ignore WordPress.Security.NonceVerification.Missing */ if ( isset( $_POST[ $name ] ) ) { /* phpcs:ignore WordPress.Security.NonceVerification.Missing */ @@ -272,6 +284,8 @@ public static function post( $name ) { * @since 3.0 */ public static function get( $name ) { + _deprecated_function( __METHOD__, '4.0' ); + /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ if ( isset( $_GET[ $name ] ) ) { /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ @@ -292,6 +306,8 @@ public static function get( $name ) { * @since 3.0 */ public static function get_pdf_filename( $form_id, $lead_id ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::get_pdf_filename()' ); + return "form-$form_id-entry-$lead_id.pdf"; } @@ -305,6 +321,8 @@ public static function get_pdf_filename( $form_id, $lead_id ) { * @since 4.0 */ public static function remove_invalid_characters( $name ) { + _deprecated_function( __METHOD__, '4.0', 'Helper_Misc::strip_invalid_characters()' ); + $misc = GPDFAPI::get_misc_class(); return $misc->strip_invalid_characters( $name ); @@ -316,6 +334,7 @@ public static function remove_invalid_characters( $name ) { * @since 3.0 */ public static function setup_ids() { + _deprecated_function( __METHOD__, '4.0' ); } } @@ -342,6 +361,8 @@ class GFPDFEntryDetail extends GFPDF_Deprecated_Abstract { * @since 3.0 */ public static function lead_detail_grid( $form, $lead, $allow_display_empty_fields = false, $show_html = false, $show_page_name = false, $should_return = false ) { + _deprecated_function( __METHOD__, '4.0' ); + $config = [ 'meta' => [ 'empty_field' => $allow_display_empty_fields, @@ -367,6 +388,7 @@ public static function lead_detail_grid( $form, $lead, $allow_display_empty_fiel * @since 3.7 */ public static function do_lead_detail_grid( $form, $lead, $config = [] ) { + _deprecated_function( __METHOD__, '4.0' ); /* Convert old config values to our new ones */ if ( ! isset( $config['meta'] ) ) { @@ -614,6 +636,8 @@ public static function load_legacy_css( GF_Field $field ) { * @since 3.0 */ public static function lead_detail_grid_array( $form, $lead ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::get_form_data()' ); + $model = GPDFAPI::get_pdf_class( 'model' ); return $model->get_form_data( $lead ); @@ -630,6 +654,8 @@ public static function lead_detail_grid_array( $form, $lead ) { * @since 3.0 */ public static function product_table( $form, $lead ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::product_table()' ); + GPDFAPI::product_table( $lead ); } @@ -645,6 +671,8 @@ public static function product_table( $form, $lead ) { * @since 3.0 */ public static function get_likert( $form, $lead, $field_id ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::likert_table()' ); + return GPDFAPI::likert_table( $lead, $field_id, true ); } } @@ -680,6 +708,8 @@ class GFPDF_Core_Model extends GFPDF_Deprecated_Abstract { * @since 3.0 */ public static function gfpdfe_save_pdf( $entry, $form ) { + _deprecated_function( __METHOD__, '4.0', 'GPDFAPI::create_pdf()' ); + $pdfs = GPDFAPI::get_entry_pdfs( $entry['id'] ); if ( ! is_wp_error( $pdfs ) ) { @@ -734,6 +764,7 @@ class mPDF { * @since 5.0 */ public function __construct( $mode = '', $format = 'A4', $default_font_size = 0, $default_font = '', $mgl = 15, $mgr = 15, $mgt = 16, $mgb = 16, $mgh = 9, $mgf = 9, $orientation = 'P' ) { + _deprecated_function( 'mPDF::__construct', '5.0', 'GFPDF\Helper\Mpdf\Mpdf' ); $data = GPDFAPI::get_data_class(); $default_font_config = ( new FontVariables() )->getDefaults(); diff --git a/tests/phpunit/integration/Controller/Test_Controller_Actions.php b/tests/phpunit/integration/Controller/Test_Controller_Actions.php index beb4cddcb..a9e20015c 100644 --- a/tests/phpunit/integration/Controller/Test_Controller_Actions.php +++ b/tests/phpunit/integration/Controller/Test_Controller_Actions.php @@ -42,10 +42,11 @@ public function test_init_registers_admin_init_hooks() { $this->assertNotFalse( has_action( 'admin_init', [ $this->controller, 'route_notices' ] ) ); } - public function test_get_routes_includes_default_core_fonts_route() { + public function test_get_routes_includes_default_routes() { $routes = $this->controller->get_routes(); $this->assertCount( 1, $routes ); + $this->assertSame( 'install_core_fonts', $routes[0]['action'] ); $this->assertSame( 'gravityforms_edit_settings', $routes[0]['capability'] ); $this->assertIsCallable( $routes[0]['condition'] ); @@ -82,6 +83,49 @@ public function test_route_notices_short_circuits_on_getting_started_page() { $this->assertFalse( $gfpdf->notices->has_notice() ); } + public function test_route_notices_skips_pages_the_notice_cannot_display_on() { + global $gfpdf, $pagenow; + + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); + set_current_screen( 'dashboard' ); + + add_filter( + 'gfpdf_one_time_action_routes', + static function () { + return [ + [ + 'action' => 'always_notify', + 'action_text' => 'Do it', + 'condition' => '__return_true', + 'process' => '__return_true', + 'view' => static function () { + return 'A notice'; + }, + 'capability' => 'gravityforms_view_settings', + ], + ]; + } + ); + + $original = $pagenow; + $pagenow = 'upload.php'; + + $gfpdf->notices->clear(); + $this->controller->route_notices(); + + $this->assertFalse( $gfpdf->notices->has_notice() ); + + $pagenow = 'index.php'; + + $this->controller->route_notices(); + + $this->assertTrue( $gfpdf->notices->has_notice() ); + + $pagenow = $original; + remove_all_filters( 'gfpdf_one_time_action_routes' ); + $gfpdf->notices->clear(); + } + public function test_route_dismisses_notice_when_dismiss_flag_set() { global $gfpdf; diff --git a/tests/phpunit/integration/Controller/Test_Controller_Install.php b/tests/phpunit/integration/Controller/Test_Controller_Install.php index ab801e558..ef50596f2 100644 --- a/tests/phpunit/integration/Controller/Test_Controller_Install.php +++ b/tests/phpunit/integration/Controller/Test_Controller_Install.php @@ -95,7 +95,7 @@ static function ( $old, $new ) use ( &$captured ) { } public function test_maybe_uninstall_emits_doing_it_wrong_notice() { - $this->setExpectedIncorrectUsage( 'GFPDF\Controller\Controller_Install::maybe_uninstall' ); + $this->setExpectedDeprecated( 'GFPDF\Controller\Controller_Install::maybe_uninstall' ); $this->controller->maybe_uninstall(); } diff --git a/tests/phpunit/integration/Controller/Test_Controller_PDF.php b/tests/phpunit/integration/Controller/Test_Controller_PDF.php index 68f9f41ab..2e371fdc6 100644 --- a/tests/phpunit/integration/Controller/Test_Controller_PDF.php +++ b/tests/phpunit/integration/Controller/Test_Controller_PDF.php @@ -126,7 +126,7 @@ public function test_prevent_index_defines_donotcachepage_constant() { } public function test_sgoptimizer_html_minification_fix_emits_doing_it_wrong() { - $this->setExpectedIncorrectUsage( 'GFPDF\Controller\Controller_PDF::sgoptimizer_html_minification_fix' ); + $this->setExpectedDeprecated( 'GFPDF\Controller\Controller_PDF::sgoptimizer_html_minification_fix' ); $this->controller->sgoptimizer_html_minification_fix(); } @@ -173,8 +173,8 @@ public function test_add_current_form_object_hooks_returns_form_unchanged_when_i * @group slow */ 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' ); + $this->setExpectedDeprecated( 'GFPDF\Controller\Controller_PDF::process_legacy_pdf_endpoint' ); + $this->setExpectedDeprecated( 'GFPDF\Model\Model_PDF::get_legacy_config' ); /* Test our endpoint is firing correctly */ $results = $this->form_and_entry(); diff --git a/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php b/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php index e2ec21ce8..90b3eb1a6 100644 --- a/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php +++ b/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php @@ -363,8 +363,8 @@ public function test_queue_dispatch_resend_notification_tasks() { * @since 5.0 */ public function test_cleanup_pdfs() { - $this->setExpectedIncorrectUsage( 'GFPDF\Statics\Queue_Callbacks::cleanup_pdfs' ); - $this->setExpectedIncorrectUsage( 'GFPDF\Model\Model_PDF::cleanup_pdf' ); + $this->setExpectedDeprecated( 'GFPDF\Statics\Queue_Callbacks::cleanup_pdfs' ); + $this->setExpectedDeprecated( 'GFPDF\Model\Model_PDF::cleanup_pdf' ); $form_class = \GPDFAPI::get_form_class(); diff --git a/tests/phpunit/integration/Controller/Test_Controller_System_Report.php b/tests/phpunit/integration/Controller/Test_Controller_System_Report.php index 2dbe45385..84d2a27f3 100644 --- a/tests/phpunit/integration/Controller/Test_Controller_System_Report.php +++ b/tests/phpunit/integration/Controller/Test_Controller_System_Report.php @@ -4,6 +4,7 @@ namespace GFPDF\Controller; +use GFPDF\Statics\Deprecation; use GFPDF\Tests\Integration\TestCase; /** @@ -88,4 +89,152 @@ public function test_system_report_outdated_template() { @unlink( $override_path ); } + + /** + * Get the items of the Deprecated Features section, which is only present when something is detected + */ + protected function get_deprecated_features(): array { + $system_report = apply_filters( 'gform_system_report', [] ); + + return $system_report[0]['tables'][4]['items'] ?? []; + } + + public function test_system_report_has_no_deprecated_features_section_by_default() { + $system_report = apply_filters( 'gform_system_report', [] ); + + $this->assertCount( 4, $system_report[0]['tables'] ); + } + + public function test_system_report_legacy_template() { + $this->assertArrayNotHasKey( 'legacy_templates', $this->get_deprecated_features() ); + + /* install a v3 template (no headers), then verify it gets flagged as legacy */ + $data = \GPDFAPI::get_data_class(); + $legacy_path = $data->template_location . 'my-legacy-template.php'; + + file_put_contents( $legacy_path, '' ); + + /* the template list is memoized per request by GFCache */ + \GFCache::flush(); + + $items = $this->get_deprecated_features(); + $this->assertArrayHasKey( 'legacy_templates', $items ); + $this->assertStringContainsString( 'my-legacy-template.php', $items['legacy_templates']['value_export'] ); + + @unlink( $legacy_path ); + + /* drop the cached list again so later tests don't read the deleted file */ + \GFCache::flush(); + } + + public function test_system_report_legacy_endpoint() { + $this->assertArrayNotHasKey( 'legacy_endpoint', $this->get_deprecated_features() ); + + Deprecation::record_legacy_endpoint_usage( 42 ); + + $items = $this->get_deprecated_features(); + $this->assertArrayHasKey( 'legacy_endpoint', $items ); + $this->assertStringContainsString( '42', $items['legacy_endpoint']['value_export'] ); + } + + public function test_system_report_advanced_templating_is_reported_as_a_legacy_template() { + $this->assertArrayNotHasKey( 'legacy_templates', $this->get_deprecated_features() ); + + $gform = \GPDFAPI::get_form_class(); + $form = $gform->get_form( $this->gf_factory()->form->create() ); + $form['gfpdf_form_settings'] = [ + 'abc123' => [ + 'id' => 'abc123', + 'name' => 'My Tier 2 PDF', + 'template' => 'zadani', + 'advanced_template' => 'Yes', + ], + ]; + + $gform->update_form( $form ); + + /* Advanced Templating has no row of its own: the template it renders is reported as a legacy one */ + $items = $this->get_deprecated_features(); + $this->assertArrayNotHasKey( 'advanced_templating', $items ); + $this->assertStringContainsString( 'zadani.php', $items['legacy_templates']['value_export'] ); + } + + public function test_system_report_deprecated_filters() { + $this->assertArrayNotHasKey( 'deprecated_filters', $this->get_deprecated_features() ); + + $callback = static function ( $name ) { + return $name; + }; + + add_filter( 'gfpdfe_pdf_filename', $callback ); + + $items = $this->get_deprecated_features(); + $this->assertArrayHasKey( 'deprecated_filters', $items ); + $this->assertStringContainsString( 'gfpdfe_pdf_filename', $items['deprecated_filters']['value_export'] ); + + remove_filter( 'gfpdfe_pdf_filename', $callback ); + } + + /** + * Get the Site Health test the controller registers + */ + protected function get_site_health_test(): array { + $tests = apply_filters( 'site_status_tests', [ 'direct' => [] ] ); + + return $tests['direct']['gravity_pdf_deprecated_features'] ?? []; + } + + public function test_site_health_test_is_registered() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); + + $test = $this->get_site_health_test(); + + $this->assertIsCallable( $test['test'] ); + $this->assertSame( 'good', call_user_func( $test['test'] )['status'] ); + } + + public function test_site_health_test_is_gated_on_the_gravity_forms_capability() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'subscriber' ] ) ); + + $this->assertSame( [], $this->get_site_health_test() ); + } + + public function test_debug_information_reports_a_clean_site() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); + + $info = apply_filters( 'debug_information', [] ); + + $this->assertSame( 'None detected', $info['gravity-pdf-deprecated']['fields']['deprecated_features']['value'] ); + } + + public function test_debug_information_is_gated_on_the_gravity_forms_capability() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'subscriber' ] ) ); + + $this->assertArrayNotHasKey( 'gravity-pdf-deprecated', apply_filters( 'debug_information', [] ) ); + } + + public function test_debug_information_reports_each_signal() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); + + Deprecation::record_legacy_endpoint_usage( 42 ); + + $fields = apply_filters( 'debug_information', [] )['gravity-pdf-deprecated']['fields']; + + $this->assertArrayNotHasKey( 'deprecated_features', $fields ); + $this->assertSame( 'Legacy PDF URLs', $fields['legacy_endpoint']['label'] ); + $this->assertStringContainsString( '42', $fields['legacy_endpoint']['value'] ); + } + + public function test_site_health_test_reports_each_signal() { + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); + + Deprecation::record_legacy_endpoint_usage( 42 ); + + $result = call_user_func( $this->get_site_health_test()['test'] ); + + $this->assertSame( 'recommended', $result['status'] ); + $this->assertStringContainsString( 'Legacy PDF URLs', $result['description'] ); + $this->assertStringContainsString( '42', $result['description'] ); + $this->assertStringContainsString( 'page=gf_system_status', $result['actions'] ); + } } diff --git a/tests/phpunit/integration/Helper/Test_Form_Data.php b/tests/phpunit/integration/Helper/Test_Form_Data.php index fe6dcea50..36782b867 100644 --- a/tests/phpunit/integration/Helper/Test_Form_Data.php +++ b/tests/phpunit/integration/Helper/Test_Form_Data.php @@ -1508,6 +1508,8 @@ public function test_poll_data() { * @since 4.0 */ public function test_empty_fields() { + $this->setExpectedDeprecated( 'GFPDFEntryDetail::lead_detail_grid_array' ); + $entry = $this->entries[6]; $form_data = GFPDFEntryDetail::lead_detail_grid_array( $this->form, $entry ); @@ -1582,6 +1584,8 @@ public function test_empty_fields() { * Ensure the Product data calculations are correct when using Euros (or similar comma/decimal switched currency) */ public function test_euro_product_data() { + $this->setExpectedDeprecated( 'GFPDFEntryDetail::lead_detail_grid_array' ); + $json = json_decode( trim( file_get_contents( PDF_PLUGIN_DIR . '/tools/phpunit/data/entries/all-form-euro-product-entry.json' ) ), true ); $json['form_id'] = $this->form['id']; $entry_id = $this->gf_factory()->entry->create($json); diff --git a/tests/phpunit/integration/Helper/Test_Gravity_Forms.php b/tests/phpunit/integration/Helper/Test_Gravity_Forms.php index 0bb72a6b7..725649844 100644 --- a/tests/phpunit/integration/Helper/Test_Gravity_Forms.php +++ b/tests/phpunit/integration/Helper/Test_Gravity_Forms.php @@ -437,6 +437,8 @@ public function test_get_entry() { * @dataProvider provider_mergetag_test */ public function test_replace_variables( $mergetag, $value ) { + $this->setExpectedDeprecated( 'PDF_Common::do_mergetags' ); + // Per-class form titles are dedup-suffixed by GFAPI (e.g. "Simple Form Testing (1)"). // Substitute the provider's expected title with the actual loaded form title. if ( $mergetag === '{form_title}' ) { diff --git a/tests/phpunit/integration/Helper/Test_MVC_Abstracts.php b/tests/phpunit/integration/Helper/Test_MVC_Abstracts.php index 3fa80d80e..7cad6d3c8 100644 --- a/tests/phpunit/integration/Helper/Test_MVC_Abstracts.php +++ b/tests/phpunit/integration/Helper/Test_MVC_Abstracts.php @@ -104,6 +104,8 @@ public function test_abstract_model() { * @since 4.0 */ public function test_abstract_view() { + $this->setExpectedDeprecated( 'GFPDF\View\View_Settings::tabs' ); + /* * Test our load function produces the correct output */ diff --git a/tests/phpunit/integration/Helper/Test_Notices.php b/tests/phpunit/integration/Helper/Test_Notices.php index 9907b8975..639221e52 100644 --- a/tests/phpunit/integration/Helper/Test_Notices.php +++ b/tests/phpunit/integration/Helper/Test_Notices.php @@ -155,6 +155,33 @@ public function test_process() { $this->assertStringContainsString( '

My First Error

', $html ); } + public function test_process_state_class() { + $this->notices->add_notice( 'My First Notice' ); + $this->notices->add_notice( 'My Warning Notice', 'notice-warning' ); + $this->notices->add_error( 'My Info Error', 'notice-info' ); + + ob_start(); + $this->notices->process(); + $html = ob_get_clean(); + + /* A `notice-*` class replaces the default state, instead of being appended to it */ + $this->assertStringContainsString( '
', $html ); + $this->assertStringContainsString( '
', $html ); + $this->assertStringContainsString( '
', $html ); + } + + public function test_process_same_class_twice() { + $this->notices->add_notice( 'My First Notice', 'notice-warning' ); + $this->notices->add_notice( 'My Second Notice', 'notice-warning' ); + + ob_start(); + $this->notices->process(); + $html = ob_get_clean(); + + $this->assertStringContainsString( '

My First Notice

', $html ); + $this->assertStringContainsString( '

My Second Notice

', $html ); + } + public function test_html_notice() { $form = 'Message

'; diff --git a/tests/phpunit/integration/Model/Test_Model_System_Report.php b/tests/phpunit/integration/Model/Test_Model_System_Report.php index 8b837b893..a16bd9555 100644 --- a/tests/phpunit/integration/Model/Test_Model_System_Report.php +++ b/tests/phpunit/integration/Model/Test_Model_System_Report.php @@ -66,7 +66,7 @@ public function test_get_report_structure() { $this->assertArrayHasKey( 'title', $structure ); $this->assertArrayHasKey( 'title_export', $structure ); $this->assertArrayHasKey( 'tables', $structure ); - $this->assertCount( 4, $structure['tables'] ); + $this->assertCount( 5, $structure['tables'] ); } public function test_move_gravitypdf_active_plugins_to_gf_addons() { diff --git a/tests/phpunit/integration/Model/Test_PDF.php b/tests/phpunit/integration/Model/Test_PDF.php index fc1f6f660..23538df99 100644 --- a/tests/phpunit/integration/Model/Test_PDF.php +++ b/tests/phpunit/integration/Model/Test_PDF.php @@ -222,8 +222,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'); + $this->setExpectedDeprecated( 'GFPDF\Controller\Controller_PDF::process_legacy_pdf_endpoint'); + $this->setExpectedDeprecated( 'GFPDF\Model\Model_PDF::get_legacy_config'); /* Force a failure */ $this->assertNull( $this->controller->process_legacy_pdf_endpoint() ); @@ -814,6 +814,7 @@ public function test_view_pdf_gravityflow_inbox() { * @since 4.0 */ public function test_get_pdf_name() { + $this->setExpectedDeprecated( 'gfpdfe_pdf_filename' ); /* Setup some test data */ $results = $this->form_and_entry(); @@ -1340,7 +1341,7 @@ public function test_cleanup_tmp_dir() { * @since 4.0 */ public function test_cleanup_pdf() { - $this->setExpectedIncorrectUsage('GFPDF\Model\Model_PDF::cleanup_pdf'); + $this->setExpectedDeprecated('GFPDF\Model\Model_PDF::cleanup_pdf'); $form_class = \GPDFAPI::get_form_class(); @@ -1559,7 +1560,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'); + $this->setExpectedDeprecated('GFPDF\Model\Model_PDF::get_legacy_config'); /* Setup some test data */ $results = $this->form_and_entry(); @@ -1590,7 +1591,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->setExpectedDeprecated('GFPDF\View\View_PDF::get_template_filename'); $this->assertSame( $expected, $this->view->get_template_filename( $template ) ); } @@ -1795,6 +1796,8 @@ public function test_show_form_title() { * @since 4.0 */ public function test_legacy_display_page_name() { + $this->setExpectedDeprecated( 'GFPDF\View\View_PDF::display_page_name' ); + $form = [ 'pagination' => [ 'pages' => [ @@ -1887,6 +1890,10 @@ public function test_page_break_field() { * @since 4.0 */ public function test_apply_backwards_compatibility_filters() { + foreach ( [ 'gfpdfe_pdf_name', 'gfpdfe_template', 'gfpdf_orientation', 'gfpdf_security', 'gfpdf_privilages', 'gfpdf_password', 'gfpdf_master_password', 'gfpdf_rtl' ] as $hook ) { + $this->setExpectedDeprecated( $hook ); + } + $entry = $this->entry( 'all-form-fields' ); $entry['form_id'] = $this->form( 'all-form-fields' )['id']; @@ -2077,6 +2084,8 @@ public function test_core_template_options() { public function test_handle_legacy_tier_2_processing() { global $gfpdf; + $this->setExpectedDeprecated( 'gfpdfe_pre_load_template' ); + $settings = [ 'id' => '556690c67856b', 'template' => 'zadani' ]; $entry = $this->entry( 'all-form-fields' ); $form = $gfpdf->gform->get_form( $entry['form_id'] ); diff --git a/tests/phpunit/integration/Statics/Test_Deprecation.php b/tests/phpunit/integration/Statics/Test_Deprecation.php new file mode 100644 index 000000000..c50038b49 --- /dev/null +++ b/tests/phpunit/integration/Statics/Test_Deprecation.php @@ -0,0 +1,177 @@ +assertSame( [], Deprecation::get_legacy_endpoint_usage() ); + + Deprecation::record_legacy_endpoint_usage( 5 ); + + $usage = Deprecation::get_legacy_endpoint_usage(); + + $this->assertSame( [ 5 ], $usage['form_ids'] ); + $this->assertEqualsWithDelta( time(), $usage['last_used'], 5 ); + } + + public function test_record_legacy_endpoint_usage_is_throttled() { + $last_used = time() - MINUTE_IN_SECONDS; + + update_option( + Deprecation::ENDPOINT_USAGE_OPTION, + [ + 'last_used' => $last_used, + 'form_ids' => [ 5 ], + ], + false + ); + + /* A form we've already recorded inside the throttle window is ignored */ + Deprecation::record_legacy_endpoint_usage( 5 ); + + $this->assertSame( $last_used, Deprecation::get_legacy_endpoint_usage()['last_used'] ); + + /* A form we haven't seen before is always recorded */ + Deprecation::record_legacy_endpoint_usage( 6 ); + + $usage = Deprecation::get_legacy_endpoint_usage(); + + $this->assertSame( [ 5, 6 ], $usage['form_ids'] ); + $this->assertNotSame( $last_used, $usage['last_used'] ); + } + + public function test_record_legacy_endpoint_usage_caps_recorded_forms() { + for ( $form_id = 1; $form_id <= Deprecation::MAX_RECORDED_FORMS + 5; $form_id++ ) { + Deprecation::record_legacy_endpoint_usage( $form_id ); + } + + $this->assertCount( Deprecation::MAX_RECORDED_FORMS, Deprecation::get_legacy_endpoint_usage()['form_ids'] ); + } + + public function test_apply_filters_skips_hooks_without_a_listener() { + $this->assertSame( 'my-pdf', Deprecation::apply_filters( 'gfpdfe_pdf_filename', [ 'my-pdf' ] ) ); + } + + public function test_apply_filters_warns_when_a_listener_is_attached() { + $this->setExpectedDeprecated( 'gfpdfe_pdf_filename' ); + + $callback = static function () { + return 'filtered-pdf'; + }; + + add_filter( 'gfpdfe_pdf_filename', $callback ); + + $this->assertSame( 'filtered-pdf', Deprecation::apply_filters( 'gfpdfe_pdf_filename', [ 'my-pdf' ] ) ); + + remove_filter( 'gfpdfe_pdf_filename', $callback ); + } + + public function test_get_active_deprecated_filters_ignores_our_own_callback() { + $this->assertNotFalse( has_filter( 'gfpdfe_pre_load_template', Deprecation::INTERNAL_FILTER_CALLBACK ) ); + $this->assertArrayNotHasKey( 'gfpdfe_pre_load_template', Deprecation::get_active_deprecated_filters() ); + } + + public function test_get_active_deprecated_filters_includes_dynamic_hooks() { + $callback = '__return_true'; + + add_filter( 'gfpdfe_pdf_template_10', $callback ); + add_filter( 'gfpdf_rtl', $callback ); + + /* The known hooks are always checked; the dynamic ones only on a deep scan */ + $this->assertArrayNotHasKey( 'gfpdfe_pdf_template_10', Deprecation::get_active_deprecated_filters() ); + $this->assertSame( 1, Deprecation::get_active_deprecated_filters()['gfpdf_rtl'] ); + + $active = Deprecation::get_active_deprecated_filters( true ); + + $this->assertSame( 1, $active['gfpdfe_pdf_template_10'] ); + $this->assertSame( 1, $active['gfpdf_rtl'] ); + + remove_filter( 'gfpdfe_pdf_template_10', $callback ); + remove_filter( 'gfpdf_rtl', $callback ); + } + + public function test_get_advanced_template_paths() { + $this->assertSame( [], Deprecation::get_advanced_template_paths() ); + + $gform = \GPDFAPI::get_form_class(); + $form = $gform->get_form( $this->gf_factory()->form->create() ); + $form['gfpdf_form_settings'] = [ + 'abc123' => [ + 'id' => 'abc123', + 'template' => 'zadani', + 'advanced_template' => 'Yes', + ], + + 'def456' => [ + 'id' => 'def456', + 'template' => 'rubix', + ], + + /* The render path reads the setting case-insensitively, so the detector has to as well */ + 'ghi789' => [ + 'id' => 'ghi789', + 'template' => 'focus-gravity', + 'advanced_template' => 'yes', + ], + + /* A PDF pointing at a template that no longer exists has nothing to upgrade */ + 'jkl012' => [ + 'id' => 'jkl012', + 'template' => 'deleted-template', + 'advanced_template' => 'Yes', + ], + ]; + + $gform->update_form( $form ); + + $this->assertSame( + [ + PDF_PLUGIN_DIR . 'src/templates/zadani.php', + PDF_PLUGIN_DIR . 'src/templates/focus-gravity.php', + ], + Deprecation::get_advanced_template_paths() + ); + + /* Advanced Templating means the template is a v3 one, even when the file carries a v4 `Group` header */ + $this->assertContains( PDF_PLUGIN_DIR . 'src/templates/zadani.php', Deprecation::get_legacy_templates() ); + + /* A trashed form isn't in the user's form list, so there's nothing for them to act on */ + \GFAPI::delete_form( $form['id'] ); + + $this->assertSame( [], Deprecation::get_advanced_template_paths() ); + } + + public function test_get_signals_omits_empty_signals() { + Deprecation::record_legacy_endpoint_usage( 5 ); + + $this->assertSame( [ 'legacy_endpoint' ], array_keys( Deprecation::get_signals() ) ); + } + + public function test_get_signals_are_cached() { + set_transient( Deprecation::SIGNAL_CACHE_KEY, [ 'cached.php' ], DAY_IN_SECONDS ); + + $this->assertSame( [ 'cached.php' ], Deprecation::get_signals()['legacy_templates'] ); + $this->assertArrayNotHasKey( 'legacy_templates', Deprecation::get_signals( true ) ); + } +} diff --git a/tests/phpunit/integration/Test_Deprecated.php b/tests/phpunit/integration/Test_Deprecated.php index be3976dd5..1ab81bf5e 100644 --- a/tests/phpunit/integration/Test_Deprecated.php +++ b/tests/phpunit/integration/Test_Deprecated.php @@ -104,6 +104,8 @@ public function test_constants() { public function test_render_save_pdf() { global $gfpdf; + $this->setExpectedDeprecated( 'PDFRender::savePDF' ); + $render = new PDFRender(); $render->savePDF( 'testing', 'mydocument.pdf', 20 ); @@ -136,6 +138,8 @@ public function test_render_prepare_ids() { * @since 4.0 */ public function test_common_get_ids() { + $this->setExpectedDeprecated( 'PDF_Common::get_ids' ); + $GLOBALS['form_id'] = '20'; $_GET['lid'] = '20,21,23'; @@ -151,6 +155,8 @@ public function test_common_get_ids() { * @since 4.0 */ public function test_common_get_pdf_filename() { + $this->setExpectedDeprecated( 'PDF_Common::get_pdf_filename' ); + $this->assertSame( 'form-50-entry-2091.pdf', PDF_Common::get_pdf_filename( 50, 2091 ) ); } @@ -163,6 +169,8 @@ public function test_common_get_pdf_filename() { public function test_deprecated_save_pdf() { global $gfpdf; + $this->setExpectedDeprecated( 'GFPDF_Core_Model::gfpdfe_save_pdf' ); + if ( is_multisite() ) { $this->markTestSkipped( 'Multisite saves the PDF under a path the prefix glob does not match (known gfpdfe_save_pdf network-site behaviour).' ); } diff --git a/tests/phpunit/integration/View/Test_View_PDF.php b/tests/phpunit/integration/View/Test_View_PDF.php index aa875989b..9ac40e817 100644 --- a/tests/phpunit/integration/View/Test_View_PDF.php +++ b/tests/phpunit/integration/View/Test_View_PDF.php @@ -123,7 +123,7 @@ public function test_show_form_title_outputs_filtered_html_when_enabled() { * @group slow */ public function test_generate_pdf() { - $this->setExpectedIncorrectUsage( 'GFPDF\View\View_PDF::generate_pdf' ); + $this->setExpectedDeprecated( 'GFPDF\View\View_PDF::generate_pdf' ); global $gfpdf; diff --git a/tests/playwright/core/system-status/deprecated-features.spec.ts b/tests/playwright/core/system-status/deprecated-features.spec.ts new file mode 100644 index 000000000..4b3b7ad29 --- /dev/null +++ b/tests/playwright/core/system-status/deprecated-features.spec.ts @@ -0,0 +1,162 @@ +import type { Admin, RequestUtils } from '@wordpress/e2e-test-utils-playwright'; +import { expect } from '@wordpress/e2e-test-utils-playwright'; +import type { Page } from '@playwright/test'; +import { test } from '@self:playwright/fixtures/test'; +import { takeSnapshot } from '@chromatic-com/playwright'; +import Pdf from '@self:playwright/utils/gravitypdf'; +import { + clearDeprecatedUsage, + recordDeprecatedUsage, +} from '@self:playwright/utils/deprecation'; + +const TIER_2_PDF = { + id: 'aaa111aaa111a', + name: 'Tier 2 PDF', + template: 'zadani', + advanced_template: 'Yes', + active: true, + filename: 'tier-2-pdf', +}; + +test.describe('Deprecated Features', () => { + // The signals are site-wide, so the tests share one set-up and run in order. + test.describe.configure({ mode: 'serial' }); + + let pdf: Pdf; + const formIds: number[] = []; + + test.beforeAll(async () => { + recordDeprecatedUsage(); + }); + + test.afterAll(async () => { + clearDeprecatedUsage(); + + // Leave the site as we found it: the PDF is the third signal, and forms outlive the run + for (const formId of formIds) { + await pdf.setFormPdfs(formId, { + aaa111aaa111a: { ...TIER_2_PDF, advanced_template: 'No' }, + }); + } + }); + + test.beforeEach( + async ({ + requestUtils, + page, + admin, + }: { + requestUtils: RequestUtils; + page: Page; + admin: Admin; + }) => { + pdf = new Pdf(requestUtils, admin, page); + + const form: any = await pdf.createForm('Advanced Templating'); + + await pdf.setFormPdfs(form.id, { aaa111aaa111a: TIER_2_PDF }); + + formIds.push(form.id); + } + ); + + test('should list each detected feature in the system report', async ({ + page, + admin, + }: { + page: Page; + admin: Admin; + }, testinfo) => { + await admin.visitAdminPage('admin.php', 'page=gf_system_status'); + + const section = page + .locator('table.gform_system_report') + .filter({ hasText: 'Deprecated Features' }); + + await expect(section).toBeVisible(); + + // The template of a PDF using Advanced Templating is reported as a legacy one + await expect(section).toContainText('zadani.php is a legacy template'); + await expect(section).toContainText( + 'Legacy PDF URLs were last requested on January 1, 2025 for form(s) 3, 12' + ); + await expect(section).toContainText('gfpdf_rtl has 1 listener(s)'); + + await section.scrollIntoViewIfNeeded(); + + await takeSnapshot(page, testinfo); + }); + + test('should report the detected features in Site Health', async ({ + page, + admin, + }: { + page: Page; + admin: Admin; + }, testinfo) => { + await admin.visitAdminPage('site-health.php'); + + const heading = page.locator('.health-check-accordion-heading', { + hasText: 'Gravity PDF functionality that is removed', + }); + + await expect(heading).toBeVisible({ timeout: 30000 }); + await heading.click(); + + const panel = page.locator( + '#health-check-accordion-block-gravity_pdf_deprecated_features' + ); + + await expect(panel).toContainText('zadani.php is a legacy template'); + await expect(panel).toContainText('gfpdf_rtl has 1 listener(s)'); + await expect( + panel.getByRole('link', { + name: 'View the Gravity Forms system report', + }) + ).toBeVisible(); + + await takeSnapshot(page, testinfo); + }); + + test.describe('Dashboard widget', () => { + // The dashboard's other widgets carry counts and dates of their own, which would churn the snapshot + test.use({ + ignoreSelectors: [ + '#wpadminbar', + '#adminmenumain', + '#dashboard_right_now', + '#dashboard_activity', + '#dashboard_primary', + ], + }); + + test('should count the detected features in the Site Health widget', async ({ + page, + admin, + }: { + page: Page; + admin: Admin; + }, testinfo) => { + // The widget reports the counts the Site Health page stores once its tests have run + await admin.visitAdminPage('site-health.php'); + await expect( + page.locator('.health-check-accordion-heading', { + hasText: 'Gravity PDF functionality that is removed', + }) + ).toBeVisible({ timeout: 30000 }); + + await admin.visitAdminPage('index.php'); + + const widget = page.locator('#dashboard_site_health'); + + await expect(widget).toContainText(/Take a look at the \d+ items/); + await expect( + widget.getByRole('link', { name: /Site Health screen/ }) + ).toBeVisible(); + + await widget.scrollIntoViewIfNeeded(); + + await takeSnapshot(page, testinfo); + }); + }); +}); diff --git a/tools/mu-plugins/gravitypdf.php b/tools/mu-plugins/gravitypdf.php index cd79e4ee6..87e2cba01 100644 --- a/tools/mu-plugins/gravitypdf.php +++ b/tools/mu-plugins/gravitypdf.php @@ -52,3 +52,19 @@ class E2E_Add_On_Bootstrap extends Helper_Abstract_Addon { }; add_action( 'init', $addon, 20 ); + +/* Give the deprecation detection a third-party filter listener to find. The callback is a pass-through, so it + changes nothing for the tests that run alongside it */ +add_action( + 'init', + static function () { + if ( get_option( 'gfpdf_e2e_deprecated_filter' ) ) { + add_filter( + 'gfpdf_rtl', + static function ( $value ) { + return $value; + } + ); + } + } +); diff --git a/tools/playwright/utils/deprecation.ts b/tools/playwright/utils/deprecation.ts new file mode 100644 index 000000000..d5ccb3c5a --- /dev/null +++ b/tools/playwright/utils/deprecation.ts @@ -0,0 +1,37 @@ +import { execSync } from 'node:child_process'; + +const ENDPOINT_USAGE_OPTION = 'gfpdf_legacy_endpoint_usage'; +const FILTER_LISTENER_OPTION = 'gfpdf_e2e_deprecated_filter'; + +// Fixed so the rendered date stays put between runs, which visual snapshots depend on. +const LAST_USED = 1735689600; // 1 January 2025 UTC + +function cli(command: string) { + execSync(`yarn wp-env:e2e run cli bash -c "${command}"`, { + stdio: ['ignore', 'pipe', 'pipe'], + }); +} + +/** + * Record use of the deprecated v3 functionality the System Report and Site Health surfaces detect. + * + * Options are the only two signals a browser can't set up on its own. The third, a legacy template, comes from a + * PDF with Advanced Templating enabled, which the caller creates over the REST API. + */ +export function recordDeprecatedUsage() { + const usage = JSON.stringify({ + last_used: LAST_USED, + form_ids: [3, 12], + }).replace(/"/g, '\\"'); + + cli( + `wp option update ${ENDPOINT_USAGE_OPTION} '${usage}' --format=json && wp option update ${FILTER_LISTENER_OPTION} 1` + ); +} + +/** + * Stop recording the deprecated usage, so the rest of the suite runs against a site without it + */ +export function clearDeprecatedUsage() { + cli(`wp option delete ${ENDPOINT_USAGE_OPTION} ${FILTER_LISTENER_OPTION}`); +} diff --git a/tools/playwright/utils/gravitypdf.ts b/tools/playwright/utils/gravitypdf.ts index 08585dc03..b98775ca5 100644 --- a/tools/playwright/utils/gravitypdf.ts +++ b/tools/playwright/utils/gravitypdf.ts @@ -53,6 +53,24 @@ export default class Pdf extends GravityForms { await this.navigateToFormSettingsById(formId, 'PDF'); } + /** + * Replace a form's PDFs over the REST API + * + * Used to configure settings the PDF form has no UI for, like the v3 Advanced Templating mode, which is only + * offered when the v3 Tier 2 add-on is installed. + */ + async setFormPdfs(formId: number, pdfs: object) { + const form: any = await this.requestUtils.rest({ + path: `/gf/v2/forms/${formId}`, + }); + + return await this.requestUtils.rest({ + method: 'PUT', + path: `/gf/v2/forms/${formId}`, + data: { ...form, gfpdf_form_settings: pdfs }, + }); + } + async createPdf(formId: number, label: string) { await this.navigateToNewFormPdf(formId); await this.page.getByLabel('Label').fill(label);