From c0d2b87b185b76d8d2f8498fe9d0e5efc8a51e08 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Fri, 14 Aug 2026 10:07:43 +1000 Subject: [PATCH] feat: detect and warn about deprecated v3 functionality ahead of 7.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v3 backwards compatibility layer is scheduled for removal in Gravity PDF 7.0, but there is currently no way to know which sites still depend on it. Legacy (v3) templates render blank under Gravity Forms 3.0 with no fatal, no warning and nothing in the debug log; the `gfpdfe_*` filters fire silently; and the deprecated methods call `_doing_it_wrong()`, which does not trigger `deprecated_function_run` and so never reaches WordPress' deprecation logging or Query Monitor. This adds detection first, so the removal in 7.0 can be judged on real data rather than assumption. New `GFPDF\Statics\Deprecation` collects three signals: - **Legacy templates** — any template Helper_Templates classifies in the `Legacy` group (no `Group:` header), plus the template of any PDF still set to `advanced_template => Yes`. 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 v4 header. The PDFs are found via a single `LIKE` over `gf_form_meta`, so sites with many forms don't load every one; forms in the trash are skipped, since they aren't in the user's form list to fix. - **Legacy PDF URLs** — `Controller_PDF::process_legacy_pdf_endpoint()` records the hit in the `gfpdf_legacy_endpoint_usage` option (autoload off), after `get_legacy_config()` has confirmed the form and PDF exist so unauthenticated requests can't drive writes. Throttled to one write an hour and capped at 20 form IDs. - **Deprecated filters** — looks the known `gfpdfe_*` hooks and v3-shaped `gfpdf_*` aliases up in `$wp_filter` by name, checking for third-party listeners. The System Report additionally walks all of `$wp_filter` to catch dynamic hooks like `gfpdfe_pdf_template_{form_id}`. Core's own `PDFRender::prepare_ids` registration is excluded via a shared constant. The template scan is cached in a day-long transient. The System Report always refreshes it. - **System Report** gains a **Deprecated Features** section listing each detected item with an upgrade link. Any section with no items is now dropped from the report, which is how this one stays hidden on a clean site. Legacy templates moved here from Outdated Templates — the two conditions have different remedies. - **Site Health** gains a test reporting the same detections, grouped by feature, with a link to the system report. This is the surface that can't be permanently dismissed: it clears itself once the site stops using the v3 layer and returns if it starts again, and it carries onto the Dashboard through the Site Health Status widget. The Info tab gains a matching section, so the detections travel in the debug export users paste into a support ticket; it's always present, reporting "None detected" on a clean site rather than leaving the reader to guess whether the check ran. Both are gated on `gravityforms_view_settings`, so the detections (form titles, PDF names, template paths) aren't exposed to administrators without Gravity Forms access. - **Log warnings** on the legacy template render path, Advanced Templating processing, and any `gfpdfe_*` filter that has a listener, so support can identify an affected site from a submitted log file. The filter warning is written once per hook per request — otherwise `gfpdfe_pdf_template` would write a line for every PDF field. The three display surfaces share the per-feature wording through `View_System_Report::get_deprecated_feature()`, which returns unescaped text: the system report and the Site Health test escape it as they build their markup, and the Info tab hands it to WordPress, which escapes it. Escaping in the builder instead would have double-encoded anything containing an apostrophe, such as a form title. Two `Helper_Notices` fixes came out of prototyping an admin notice for this, which was dropped in favour of the two Site Health surfaces. Both apply to the pre-existing `view_class` route key, which any add-on can already use: a caller-supplied `notice-*` class now replaces the default `updated` state rather than being appended to it (`div.updated` out-specifies `.notice-warning`, so an appended warning still rendered green), and queued notices are stored as message/class pairs rather than keyed by their class, which silently dropped a second notice sharing the same one. `Controller_Actions::route_notices()` also returns early on the admin pages where `Helper_Notices` discards the notice anyway, so route conditions no longer run on every admin request. - `_doing_it_wrong()` becomes `_deprecated_function()` / `_deprecated_argument()` across the Tier B/C/E surface, so these now fire `deprecated_function_run` and land in WordPress' deprecation log and Query Monitor. Bespoke prose messages collapse into the `$replacement` argument. - The ~20 silent stubs deprecated in 6.0-6.12 (`Model_Settings` font methods, `Model_Install` uninstall proxies, `Helper_Notices`, the `pdf.php` canonical-release notices) now emit a notice instead of returning quietly. - The v3 shim classes in `src/deprecated.php` warn from their implemented methods, not just the `__call` fallback — including the `mPDF` shim constructor, so a v3 template calling `new mPDF()` is no longer silent. The `__call` / `__callStatic` fallbacks now name `static::class . '::' . $name` rather than the bare method. - The last uncovered Tier E items warn too: `View_PDF::display_page_name()`, `View_Settings::tabs()`, and the `$args['content']` view fallback — warned once from `Helper_Abstract_View::load()`, which already knows the view name, rather than from each of the six templates that read it. `Helper_Misc::flatten_array()` and `Helper_Abstract_Addon::plugin_updater()` stay on `_doing_it_wrong()` — they report misuse, not deprecation. - The `gfpdfe_*` filters route through `Deprecation::apply_filters()`, a thin wrapper over `apply_filters_deprecated()` that shares the hook -> replacement map with the scanner. It early-returns when no listener is attached, so the PDF render path pays nothing on sites that have already moved on. - New `Test_Deprecation` covers the static directly: endpoint recorder throttling and form cap, the listener scan ignoring core's own `gfpdfe_pre_load_template` registration while catching dynamic hooks, and signal caching. - New `Test_Controller_System_Report` coverage for each of the three signals, for Advanced Templating landing in the legacy template row rather than one of its own, and for the registration, capability gate and reported detections of both Site Health surfaces. - `setExpectedIncorrectUsage()` -> `setExpectedDeprecated()` wherever the primitive changed; tests that reach a deprecated path incidentally now declare it. - New Playwright coverage with Chromatic snapshots of all three new screens: the system report section, the Site Health test expanded, and the Site Health dashboard widget carrying the counts. The recorded legacy URL is pinned to a fixed date so the snapshots don't churn daily. Signals are seeded through two options and a PDF created over REST, then cleared afterwards, so the rest of the suite runs against a site without them. - Integration suite: 1593 tests, 0 failures, 46 skipped. PHPCS and PHP compatibility clean. Co-Authored-By: Claude Opus 5 (1M context) --- pdf.php | 8 +- src/Controller/Controller_Actions.php | 12 +- src/Controller/Controller_Install.php | 2 +- src/Controller/Controller_PDF.php | 12 +- src/Controller/Controller_Pdf_Queue.php | 6 +- src/Controller/Controller_System_Report.php | 71 ++- src/Helper/Fields/Field_Signature.php | 3 +- src/Helper/Helper_Abstract_View.php | 4 + src/Helper/Helper_Misc.php | 2 +- src/Helper/Helper_Notices.php | 65 +-- src/Helper/Helper_Options_Fields.php | 5 +- src/Helper/Helper_PDF.php | 13 +- src/Helper/Helper_Templates.php | 16 +- src/Model/Model_Install.php | 15 +- src/Model/Model_PDF.php | 55 +-- src/Model/Model_Settings.php | 38 +- src/Model/Model_Shortcodes.php | 2 +- src/Model/Model_System_Report.php | 66 ++- src/Model/Model_Templates.php | 2 +- src/Statics/Deprecation.php | 418 ++++++++++++++++++ src/Statics/Queue_Callbacks.php | 2 +- src/View/View_PDF.php | 31 +- src/View/View_Settings.php | 2 + src/View/View_System_Report.php | 227 +++++++++- src/bootstrap.php | 7 +- src/deprecated.php | 35 +- .../Controller/Test_Controller_Actions.php | 46 +- .../Controller/Test_Controller_Install.php | 2 +- .../Controller/Test_Controller_PDF.php | 6 +- .../Controller/Test_Controller_Pdf_Queue.php | 4 +- .../Test_Controller_System_Report.php | 149 +++++++ .../integration/Helper/Test_Form_Data.php | 4 + .../integration/Helper/Test_Gravity_Forms.php | 2 + .../integration/Helper/Test_MVC_Abstracts.php | 2 + .../integration/Helper/Test_Notices.php | 27 ++ .../Model/Test_Model_System_Report.php | 2 +- tests/phpunit/integration/Model/Test_PDF.php | 19 +- .../integration/Statics/Test_Deprecation.php | 177 ++++++++ tests/phpunit/integration/Test_Deprecated.php | 8 + .../integration/View/Test_View_PDF.php | 2 +- .../system-status/deprecated-features.spec.ts | 162 +++++++ tools/mu-plugins/gravitypdf.php | 16 + tools/playwright/utils/deprecation.ts | 37 ++ tools/playwright/utils/gravitypdf.ts | 18 + 44 files changed, 1682 insertions(+), 120 deletions(-) create mode 100644 src/Statics/Deprecation.php create mode 100644 tests/phpunit/integration/Statics/Test_Deprecation.php create mode 100644 tests/playwright/core/system-status/deprecated-features.spec.ts create mode 100644 tools/playwright/utils/deprecation.ts 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'] .= '
  • ' . implode( '
  • ', array_map( 'esc_html', $feature['lines'] ) ) . '
'; + $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);