diff --git a/src/Controller/Controller_Actions.php b/src/Controller/Controller_Actions.php
index 8bdf91f21..ecf5cfe04 100644
--- a/src/Controller/Controller_Actions.php
+++ b/src/Controller/Controller_Actions.php
@@ -150,7 +150,7 @@ public function get_routes() {
'action' => 'failed_core_fonts',
'action_text' => esc_html__( 'View the system report', 'gravity-pdf' ),
'condition' => [ $this->model, 'has_failed_core_fonts' ],
- 'process' => [ $this->model, 'system_report_redirect' ],
+ 'process' => [ $this->model, 'core_fonts_report_redirect' ],
'dismiss' => [ $this->model, 'dismiss_failed_core_fonts' ],
'view' => function ( $action, $button_text ) {
return $this->view->failed_core_fonts( $this->model->get_undismissed_failed_core_fonts(), $action, $button_text );
diff --git a/src/Controller/Controller_System_Report.php b/src/Controller/Controller_System_Report.php
index aaf0bc462..b95e5276e 100644
--- a/src/Controller/Controller_System_Report.php
+++ b/src/Controller/Controller_System_Report.php
@@ -106,6 +106,11 @@ public function site_status_tests( $tests ) {
'test' => [ $this, 'cache_cleanup_test' ],
];
+ $tests['direct']['gravity_pdf_core_fonts'] = [
+ 'label' => esc_html__( 'Gravity PDF core fonts', 'gravity-pdf' ),
+ 'test' => [ $this, 'core_fonts_test' ],
+ ];
+
return $tests;
}
@@ -119,6 +124,16 @@ public function cache_cleanup_test() {
return $this->view->get_cache_cleanup_test( $this->model->is_cache_cleanup_stalled() );
}
+ /**
+ * Run the core fonts Site Health test
+ *
+ * @return array
+ * @since 6.19.0
+ */
+ public function core_fonts_test() {
+ return $this->view->get_core_fonts_test( $this->model->get_core_font_health() );
+ }
+
/**
* Run the deprecated functionality Site Health test
*
diff --git a/src/Model/Model_Actions.php b/src/Model/Model_Actions.php
index 8bc3f3fe2..e6df39a45 100644
--- a/src/Model/Model_Actions.php
+++ b/src/Model/Model_Actions.php
@@ -334,4 +334,14 @@ public function system_report_redirect() {
wp_safe_redirect( Model_System_Report::get_report_url() );
exit;
}
+
+ /**
+ * Send the user to the system report row listing the failed core fonts
+ *
+ * @since 6.19.0
+ */
+ public function core_fonts_report_redirect() {
+ wp_safe_redirect( Model_System_Report::get_report_url( Model_System_Report::FONTS_FAILED_ANCHOR ) );
+ exit;
+ }
}
diff --git a/src/Model/Model_Font_Downloader.php b/src/Model/Model_Font_Downloader.php
index 9f42b658e..e9181c514 100644
--- a/src/Model/Model_Font_Downloader.php
+++ b/src/Model/Model_Font_Downloader.php
@@ -280,6 +280,61 @@ protected function get_auto_install_constant(): ?bool {
return defined( 'GPDF_AUTO_INSTALL_FONTS' ) ? (bool) constant( 'GPDF_AUTO_INSTALL_FONTS' ) : null;
}
+ /**
+ * Whether the manifest's files may be downloaded, whichever are missing, and what turned them off
+ *
+ * @since 6.19.0
+ */
+ public function get_auto_install_status(): string {
+ if ( $this->get_auto_install_constant() === false ) {
+ return 'Off (GPDF_AUTO_INSTALL_FONTS constant)';
+ }
+
+ if ( ! $this->is_auto_install_overridden() ) {
+ return 'On';
+ }
+
+ $files = array_keys( $this->font_sources->get_manifest() );
+ $blocked = array_filter(
+ $files,
+ function ( $file ) {
+ return ! $this->is_auto_install_enabled( (string) $file );
+ }
+ );
+
+ if ( $blocked === [] ) {
+ return 'On';
+ }
+
+ return sprintf( '%s (gfpdf_auto_install_fonts filter)', count( $blocked ) === count( $files ) ? 'Off' : 'Partly off' );
+ }
+
+ /**
+ * What stops the missing required files downloading, or null
+ *
+ * @param string[] $missing The missing required files
+ *
+ * @since 6.19.0
+ */
+ public function get_auto_install_blocker( array $missing ): ?string {
+ if ( $this->can_download_all( $missing ) ) {
+ return null;
+ }
+
+ return $this->get_auto_install_constant() === false ? 'GPDF_AUTO_INSTALL_FONTS constant' : 'gfpdf_auto_install_fonts filter';
+ }
+
+ /**
+ * Whether the constant turned downloads off or the filter may have, so a file can be blocked
+ *
+ * @since 6.19.0
+ */
+ public function is_auto_install_overridden(): bool {
+ $locked = $this->get_auto_install_constant();
+
+ return $locked === null ? has_filter( 'gfpdf_auto_install_fonts' ) : ! $locked;
+ }
+
/**
* Whether a Font Manager record of this site uses the file, so it isn't the core font's
*
@@ -1470,7 +1525,7 @@ protected function save_failures( array $failures ): void {
}
/**
- * Failures by file, read once per request
+ * Failures by file, read once per request, when any give-up whose file is now on disk is cleared from the record
*
* @return array ' . $intro . ' ' . $intro . ' ' . esc_html( View_Actions::get_missing_core_fonts_warning() ) . ' ' . esc_html__( 'Install the fonts by hand', 'gravity-pdf' ) . ' ' . esc_html__( 'Fonts that failed to download:', 'gravity-pdf' ) . ' ' . $off . ' ' . esc_html__( 'Learn how to turn font downloads back on', 'gravity-pdf' ) . ' ' . esc_html__( 'View the Gravity Forms system report', 'gravity-pdf' ) . ' ' . esc_html__( 'View the Gravity Forms system report', 'gravity-pdf' ) . '' . esc_html( $item ) . '' . $html . '
';
+ }
}
diff --git a/src/View/View_System_Report.php b/src/View/View_System_Report.php
index 0ecc84006..84d5a0191 100644
--- a/src/View/View_System_Report.php
+++ b/src/View/View_System_Report.php
@@ -502,6 +502,61 @@ public function get_cache_cleanup_test( bool $stalled ): array {
return $result;
}
+ /**
+ * Build the Site Health result for the core fonts the site's PDFs need
+ *
+ * @param array $health See Model_System_Report::get_core_font_health()
+ *
+ * @since 6.19.0
+ */
+ public function get_core_fonts_test( array $health ): array {
+ $intro = esc_html__( 'Gravity PDF automatically downloads fonts needed to correctly render PDFs.', 'gravity-pdf' );
+
+ $result = [
+ 'label' => esc_html__( 'Gravity PDF has the core fonts your PDFs need', 'gravity-pdf' ),
+ 'status' => 'good',
+ 'badge' => $this->get_site_health_badge(),
+ 'description' => '$0', esc_html( $health['blocker'] ) ) );
+ if ( $others !== [] ) {
+ $off .= ' ' . esc_html__( 'These fonts are missing:', 'gravity-pdf' );
+ $list = View_Actions::get_code_list( $others );
+ }
+
+ $result['description'] .= '
-
-
+
diff --git a/src/bootstrap.php b/src/bootstrap.php
index 70da89717..2e841e1c9 100644
--- a/src/bootstrap.php
+++ b/src/bootstrap.php
@@ -941,7 +941,8 @@ public function check_system_status() {
$this->templates,
$this->singleton->get_class( 'Model_Pdf_Cache' ),
$this->singleton->get_class( 'Model_Required_Fonts' ),
- $this->singleton->get_class( 'Model_Font_Sources' )
+ $this->singleton->get_class( 'Model_Font_Sources' ),
+ $this->singleton->get_class( 'Model_Font_Downloader' )
);
$class = new Controller\Controller_System_Report( $model, $view, $this->gform );
$class->init();
diff --git a/tests/phpunit/Concerns/Seamed_Font_Downloader.php b/tests/phpunit/Concerns/Seamed_Font_Downloader.php
index 86e12fb1b..77bad9546 100644
--- a/tests/phpunit/Concerns/Seamed_Font_Downloader.php
+++ b/tests/phpunit/Concerns/Seamed_Font_Downloader.php
@@ -11,7 +11,8 @@
use GFPDF_Vendor\Psr\Log\LoggerInterface;
/**
- * A downloader with its seams set: `sources`, `budget` (seconds), `jitter` (seconds) and `required` files
+ * A downloader with its seams set: `sources`, `budget` (seconds), `jitter` (seconds), `required` files and
+ * `constant` (GPDF_AUTO_INSTALL_FONTS, null when undefined)
*/
class Seamed_Font_Downloader extends Model_Font_Downloader {
@@ -20,10 +21,15 @@ class Seamed_Font_Downloader extends Model_Font_Downloader {
*/
public $hashed = [];
+ /**
+ * @var bool|null The `constant` seam
+ */
+ private $constant;
+
/**
* Build the downloader with the real dependencies, overriding the seams given
*
- * @param array{sources?: Model_Font_Sources, budget?: int|float, jitter?: int, required?: string[]} $seams
+ * @param array{sources?: Model_Font_Sources, budget?: int|float, jitter?: int, required?: string[], constant?: bool|null} $seams
*/
public function __construct( LoggerInterface $log, array $seams = [] ) {
$sources = $seams['sources'] ?? GPDFAPI::get_mvc_class( 'Model_Font_Sources' );
@@ -42,6 +48,21 @@ public function get_required_font_files(): array {
$this->sync_budget = $seams['budget'] ?? $this->sync_budget;
$this->retry_jitter = $seams['jitter'] ?? $this->retry_jitter;
+ $this->constant = $seams['constant'] ?? null;
+ }
+
+ /**
+ * The `constant` seam when given, else the real constant
+ */
+ protected function get_auto_install_constant(): ?bool {
+ return $this->constant ?? parent::get_auto_install_constant();
+ }
+
+ /**
+ * The required fonts model holding the `required` seam
+ */
+ public function get_required_fonts(): Model_Required_Fonts {
+ return $this->required_fonts;
}
/**
diff --git a/tests/phpunit/integration/Controller/Test_Controller_Actions.php b/tests/phpunit/integration/Controller/Test_Controller_Actions.php
index 5cf43f396..fdc1a9040 100644
--- a/tests/phpunit/integration/Controller/Test_Controller_Actions.php
+++ b/tests/phpunit/integration/Controller/Test_Controller_Actions.php
@@ -231,7 +231,7 @@ function () {
$this->set_font_failures( [ 'Garuda.ttf' => $this->failure( time() - 10 ), 'FreeSans.ttf' => $this->failure() ] );
$html = $this->render_notices();
- $this->assertStringContainsString( 'Gravity PDF could not download these fonts. Until they are installed, PDFs use a fallback font in their place', $html );
+ $this->assertStringContainsString( 'Gravity PDF could not download these fonts. Until the fonts are installed, PDFs that need them use a fallback font', $html );
$this->assertStringContainsString( 'Garuda.ttf', $html );
$this->assertStringNotContainsString( 'FreeSans.ttf', $html );
$this->assertStringContainsString( 'https://docs.gravitypdf.com/users/core-pdf-fonts', $html );
diff --git a/tests/phpunit/integration/Controller/Test_Controller_System_Report.php b/tests/phpunit/integration/Controller/Test_Controller_System_Report.php
index 0bcf89a8f..80b5be5d7 100644
--- a/tests/phpunit/integration/Controller/Test_Controller_System_Report.php
+++ b/tests/phpunit/integration/Controller/Test_Controller_System_Report.php
@@ -9,8 +9,11 @@
use GFPDF\Model\Model_System_Report;
use GFPDF\Statics\Deprecation;
use GFPDF\Tests\Concerns\AccessesPrivateState;
+use GFPDF\Tests\Concerns\CopiesFontFixtures;
use GFPDF\Tests\Concerns\CreatesLegacyDownloadUrls;
use GFPDF\Tests\Concerns\CreatesLegacyTemplates;
+use GFPDF\Tests\Concerns\RecordsFontFailures;
+use GFPDF\Tests\Concerns\Seamed_Font_Downloader;
use GFPDF\Tests\Concerns\StubsFontSources;
use GFPDF\Tests\Integration\TestCase;
@@ -32,7 +35,9 @@ class Test_Controller_System_Report extends TestCase {
use AccessesPrivateState;
use CreatesLegacyDownloadUrls;
+ use CopiesFontFixtures;
use CreatesLegacyTemplates;
+ use RecordsFontFailures;
use StubsFontSources;
public function set_up(): void {
@@ -44,6 +49,14 @@ public function set_up(): void {
public function tear_down(): void {
remove_filter( 'pre_http_request', [ $this, 'get_public_dir_api_response' ] );
+ /* Null falls back to the registered models, which are what bootstrap injected */
+ $report = \GPDFAPI::get_mvc_class( 'Model_System_Report' );
+ $this->set_private( $report, 'font_downloader', null );
+ $this->set_private( $report, 'required_fonts', null );
+
+ $this->remove_font_fixtures();
+ $this->delete_font_failures();
+
parent::tear_down();
}
@@ -426,10 +439,10 @@ public function test_system_report_tools_tab_hooks() {
/**
* Get the Site Health test the controller registers
*/
- protected function get_site_health_test(): array {
+ protected function get_site_health_test( string $id = 'gravity_pdf_deprecated_features' ): array {
$tests = apply_filters( 'site_status_tests', [ 'direct' => [] ] );
- return $tests['direct']['gravity_pdf_deprecated_features'] ?? [];
+ return $tests['direct'][ $id ] ?? [];
}
public function test_site_health_test_is_registered() {
@@ -639,4 +652,158 @@ public function test_the_pdf_cache_row_and_site_health_warn_when_cron_is_not_cle
rmdir( $entry_dir );
}
}
+
+ public function test_core_fonts_failed_lists_given_up_files_even_after_the_notice_is_dismissed() {
+ update_option( 'date_format', 'Y-m-d' );
+ $this->set_font_failures(
+ [
+ 'Garuda.ttf' => $this->failure( strtotime( '2026-11-03 12:00:00 UTC' ) ),
+ 'FreeSans.ttf' => $this->failure(),
+ ]
+ );
+
+ $this->assertSame( 'Core Fonts failed', $this->report_row( 'core_fonts_failed' )['label_export'] );
+ $this->assertSame( 'Garuda.ttf (gave up 2026-11-03)', $this->report_row( 'core_fonts_failed' )['value'] );
+
+ \GPDFAPI::get_mvc_class( 'Model_Actions' )->dismiss_failed_core_fonts();
+
+ $this->assertFalse( \GPDFAPI::get_mvc_class( 'Model_Actions' )->has_failed_core_fonts() );
+ $this->assertSame( 'Garuda.ttf (gave up 2026-11-03)', $this->report_row( 'core_fonts_failed' )['value'] );
+ }
+
+ public function test_core_fonts_failed_and_the_notice_clear_once_the_font_is_on_disk() {
+ $this->list_fixture_as( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->copy_fixture( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->set_font_failures( [ 'Garuda.ttf' => $this->failure( time() ) ] );
+
+ $this->assertSame( 'None', $this->report_row( 'core_fonts_failed' )['value'] );
+ $this->assertFalse( \GPDFAPI::get_mvc_class( 'Model_Actions' )->has_failed_core_fonts() );
+ }
+
+ public function test_the_font_rows_can_be_linked_to() {
+ $this->use_report_downloader( [] );
+
+ $this->assertStringContainsString( 'id="' . Model_System_Report::FONTS_FAILED_ANCHOR . '"', $this->report_row( 'core_fonts_failed' )['label'] );
+ $this->assertStringContainsString( 'id="' . Model_System_Report::FONT_DOWNLOADS_ANCHOR . '"', $this->report_row( 'core_font_downloads' )['label'] );
+ }
+
+ public function test_core_fonts_failed_says_none_without_a_given_up_file() {
+ $this->set_font_failures( [ 'Garuda.ttf' => $this->failure() ] );
+
+ $this->assertSame( 'None', $this->report_row( 'core_fonts_failed' )['value'] );
+ }
+
+ public function test_core_font_downloads_reads_the_downloaders_status() {
+ $this->use_report_downloader( [ 'Garuda.ttf' ] );
+
+ $this->assertSame( 'Core Font downloads', $this->report_row( 'core_font_downloads' )['label_export'] );
+ $this->assertSame( 'On', $this->report_row( 'core_font_downloads' )['value'] );
+
+ add_filter( 'gfpdf_auto_install_fonts', '__return_false' );
+
+ $this->assertSame( 'Off (gfpdf_auto_install_fonts filter)', $this->report_row( 'core_font_downloads' )['value'] );
+ }
+
+ public function test_the_core_fonts_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( 'gravity_pdf_core_fonts' ) );
+ }
+
+ public function test_the_core_fonts_site_health_test_is_good_with_every_required_file_on_disk() {
+ $this->list_fixture_as( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->copy_fixture( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->use_report_downloader( [ 'Garuda.ttf' ] );
+
+ /* A file that gave up but has since been copied in by hand no longer counts */
+ $this->set_font_failures( [ 'Garuda.ttf' => $this->failure( time() ) ] );
+ add_filter( 'gfpdf_auto_install_fonts', '__return_false' );
+
+ $result = $this->run_core_fonts_test();
+
+ $this->assertSame( 'good', $result['status'] );
+ $this->assertSame( 'gravity_pdf_core_fonts', $result['test'] );
+ }
+
+ public function test_the_core_fonts_site_health_test_is_good_while_missing_files_wait_to_download() {
+ $this->use_report_downloader( [ 'Garuda.ttf' ] );
+ $this->set_font_failures( [ 'Garuda.ttf' => $this->failure() ] );
+
+ $this->assertSame( 'good', $this->run_core_fonts_test()['status'] );
+ }
+
+ public function test_the_core_font_health_only_scans_the_forms_when_a_file_could_be_reported() {
+ $this->use_report_downloader( [ 'Garuda.ttf' ] );
+ $report = \GPDFAPI::get_mvc_class( 'Model_System_Report' );
+
+ $this->assertSame( [], $report->get_core_font_health()['missing'] );
+
+ add_filter( 'gfpdf_auto_install_fonts', '__return_true' );
+
+ $this->assertSame( [ 'Garuda.ttf' ], $report->get_core_font_health()['missing'] );
+ }
+
+ public function test_the_core_fonts_site_health_test_is_critical_for_a_file_that_gave_up() {
+ $this->use_report_downloader( [ 'Garuda.ttf', 'FreeSans.ttf' ] );
+ $this->set_font_failures( [ 'Garuda.ttf' => $this->failure( time() ) ] );
+
+ $result = $this->run_core_fonts_test();
+
+ $this->assertSame( 'critical', $result['status'] );
+ $this->assertSame( 'Gravity PDF could not download required fonts', $result['label'] );
+ $this->assertStringContainsString( 'render PDFs. Until the fonts are installed', $result['description'] );
+ $this->assertStringContainsString( 'may not display.
Fonts that failed to download:
Garuda.ttf', $result['description'] );
+ $this->assertStringNotContainsString( 'turned off', $result['description'] );
+ $this->assertStringContainsString( 'page=gf_system_status#' . Model_System_Report::FONTS_FAILED_ANCHOR, $result['actions'] );
+ $this->assertStringNotContainsString( 'gfpdf_auto_install_fonts', $result['actions'] );
+
+ add_filter( 'gfpdf_auto_install_fonts', '__return_false' );
+
+ $this->assertSame( 'critical', $this->run_core_fonts_test()['status'] );
+ }
+
+ public function test_the_core_fonts_site_health_test_recommends_action_for_missing_files_while_downloads_are_off() {
+ $this->use_report_downloader( [ 'Garuda.ttf' ] );
+ add_filter( 'gfpdf_auto_install_fonts', '__return_false' );
+
+ $result = $this->run_core_fonts_test();
+
+ $this->assertSame( 'recommended', $result['status'] );
+ $this->assertSame( 'Some core fonts your PDFs need are missing', $result['label'] );
+ $this->assertStringContainsString( 'turned off by gfpdf_auto_install_fonts filter. These fonts are missing:Garuda.ttf', $result['description'] );
+ $this->assertStringContainsString( 'render PDFs. Until the fonts are installed', $result['description'] );
+ $this->assertStringContainsString( 'page=gf_system_status#' . Model_System_Report::FONT_DOWNLOADS_ANCHOR, $result['actions'] );
+ $this->assertStringContainsString( 'gfpdf_auto_install_fonts', $result['actions'] );
+ }
+
+ /**
+ * Give the registered report a downloader whose required set is the files given
+ *
+ * @param string[] $files
+ */
+ private function use_report_downloader( array $files ): void {
+ $report = \GPDFAPI::get_mvc_class( 'Model_System_Report' );
+ $downloader = new Seamed_Font_Downloader( \GPDFAPI::get_log_class(), [ 'required' => $files ] );
+
+ $this->set_private( $report, 'font_downloader', $downloader );
+ $this->set_private( $report, 'required_fonts', $downloader->get_required_fonts() );
+ }
+
+ /**
+ * Run the core fonts Site Health test as an administrator
+ */
+ private function run_core_fonts_test(): array {
+ wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) );
+
+ return call_user_func( $this->get_site_health_test( 'gravity_pdf_core_fonts' )['test'] );
+ }
+
+ /**
+ * A row of the report's directories section
+ */
+ private function report_row( string $key ): array {
+ return $this->get_report_section( 'directories' )[ $key ];
+ }
}
diff --git a/tests/phpunit/integration/Model/Test_Model_Font_Downloader.php b/tests/phpunit/integration/Model/Test_Model_Font_Downloader.php
index 62c1c4570..45d6e6a2a 100644
--- a/tests/phpunit/integration/Model/Test_Model_Font_Downloader.php
+++ b/tests/phpunit/integration/Model/Test_Model_Font_Downloader.php
@@ -427,15 +427,7 @@ static function () use ( $filtered ) {
);
$this->serve_font_downloads();
- $downloader = new class( ...$this->downloader_args() ) extends Model_Font_Downloader {
- /** @var bool|null */
- public $constant;
-
- protected function get_auto_install_constant(): ?bool {
- return $this->constant;
- }
- };
- $downloader->constant = $constant;
+ $downloader = $this->seamed( [ 'constant' => $constant ] );
$this->assertSame( $downloads, $downloader->download( self::FONT ) );
$this->assertCount( $downloads ? 1 : 0, $this->requested );
@@ -608,6 +600,125 @@ public function test_prune_never_removes_a_hashed_copy_or_a_replaced_file() {
$this->assertFileExists( $this->font_path( $sources->get_hashed_name( 'DejaVuSans.ttf' ) ) );
}
+ public function test_the_missing_files_are_the_required_files_not_on_disk() {
+ $this->list_fixture_as( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->copy_fixture( 'Chewy.ttf', 'Garuda.ttf' );
+
+ $this->assertSame( [ self::FONT ], $this->seamed( [ 'required' => [ 'Garuda.ttf', self::FONT ] ] )->get_required_fonts()->get_missing_files() );
+ }
+
+ public function test_a_file_another_font_holds_the_name_of_is_missing_until_its_hashed_copy_is_saved() {
+ $this->put_other_font_at( self::FONT );
+ $required = $this->seamed( [ 'required' => [ self::FONT ] ] )->get_required_fonts();
+
+ $this->assertSame( [ self::FONT ], $required->get_missing_files() );
+
+ $this->copy_fixture( self::FONT, GPDFAPI::get_mvc_class( 'Model_Font_Sources' )->get_hashed_name( self::FONT ) );
+ $this->assertSame( [], $required->get_missing_files() );
+ }
+
+ public function test_the_auto_install_status_does_not_depend_on_the_missing_files() {
+ add_filter( 'gfpdf_auto_install_fonts', '__return_false' );
+
+ $downloader = $this->seamed( [ 'required' => [] ] );
+ $this->assertSame( 'Off (gfpdf_auto_install_fonts filter)', $downloader->get_auto_install_status() );
+ $this->assertNull( $downloader->get_auto_install_blocker( [] ) );
+ }
+
+ public function test_the_blocker_names_the_constant() {
+ $downloader = $this->seamed(
+ [
+ 'constant' => false,
+ 'required' => [],
+ ]
+ );
+
+ $this->assertSame( 'GPDF_AUTO_INSTALL_FONTS constant', $downloader->get_auto_install_blocker( [ 'Garuda.ttf' ] ) );
+ }
+
+ public function test_the_auto_install_status_only_walks_the_manifest_when_a_filter_could_block_a_file() {
+ $sources = new class( GPDFAPI::get_data_class(), GPDFAPI::get_mvc_class( 'Model_Custom_Fonts' ), GPDFAPI::get_log_class() ) extends Model_Font_Sources {
+ /** @var int */
+ public $reads = 0;
+
+ public function get_manifest(): array {
+ $this->reads++;
+
+ return parent::get_manifest();
+ }
+ };
+
+ $downloader = $this->seamed( [ 'sources' => $sources ] );
+
+ $this->assertSame( 'On', $downloader->get_auto_install_status() );
+ $this->assertSame( 0, $sources->reads );
+
+ add_filter( 'gfpdf_auto_install_fonts', '__return_true' );
+
+ $this->assertSame( 'On', $downloader->get_auto_install_status() );
+ $this->assertSame( 1, $sources->reads );
+ }
+
+ public function test_the_auto_install_status_is_partly_off_when_the_filter_blocks_some_files() {
+ $this->list_fixture_as( 'Chewy.ttf', 'Garuda.ttf' );
+ $this->copy_fixture( 'Chewy.ttf', 'Garuda.ttf' );
+ add_filter(
+ 'gfpdf_auto_install_fonts',
+ static function ( $download, $file ) {
+ return $file !== 'Garuda.ttf';
+ },
+ 10,
+ 2
+ );
+
+ $downloader = $this->seamed( [ 'required' => [ 'Garuda.ttf', self::FONT ] ] );
+ $this->assertSame( 'Partly off (gfpdf_auto_install_fonts filter)', $downloader->get_auto_install_status() );
+
+ /* Installed, so nothing it blocks is needed yet */
+ $this->assertNull( $downloader->get_auto_install_blocker( $downloader->get_required_fonts()->get_missing_files() ) );
+
+ wp_delete_file( $this->font_path( 'Garuda.ttf' ) );
+ $this->assertSame( 'gfpdf_auto_install_fonts filter', $downloader->get_auto_install_blocker( $downloader->get_required_fonts()->get_missing_files() ) );
+ }
+
+ /**
+ * @dataProvider provider_auto_install_status
+ *
+ * @param bool|null $constant GPDF_AUTO_INSTALL_FONTS, or null when it isn't defined
+ */
+ public function test_the_auto_install_status_names_what_turned_downloads_off( ?bool $constant, bool $filtered, string $expected ) {
+ add_filter( 'gfpdf_auto_install_fonts', $filtered ? '__return_true' : '__return_false' );
+
+ $downloader = $this->seamed(
+ [
+ 'required' => [ self::FONT ],
+ 'constant' => $constant,
+ ]
+ );
+
+ $this->assertSame( $expected, $downloader->get_auto_install_status() );
+ }
+
+ public function test_downloads_are_overridden_by_a_constant_turning_them_off_or_any_filter() {
+ $this->assertFalse( $this->seamed( [] )->is_auto_install_overridden() );
+ $this->assertFalse( $this->seamed( [ 'constant' => true ] )->is_auto_install_overridden() );
+ $this->assertTrue( $this->seamed( [ 'constant' => false ] )->is_auto_install_overridden() );
+
+ add_filter( 'gfpdf_auto_install_fonts', '__return_true' );
+
+ $this->assertTrue( $this->seamed( [] )->is_auto_install_overridden() );
+ $this->assertFalse( $this->seamed( [ 'constant' => true ] )->is_auto_install_overridden() );
+ }
+
+ public function provider_auto_install_status(): array {
+ return [
+ 'the constant on, the filter off' => [ true, false, 'On' ],
+ 'the constant off, the filter on' => [ false, true, 'Off (GPDF_AUTO_INSTALL_FONTS constant)' ],
+ 'no constant, the filter off' => [ null, false, 'Off (gfpdf_auto_install_fonts filter)' ],
+ 'no constant, the filter on' => [ null, true, 'On' ],
+ ];
+ }
+
public function test_prune_removes_unmodified_core_fonts_and_licences() {
$this->set_installer_manifest( array_combine( static::$core_fixtures, static::$core_fixtures ) + [ 'DejaVuinfo.txt' => 'chewy-LICENSE.txt' ] );
$this->copy_fixture( 'chewy-LICENSE.txt', 'DejaVuinfo.txt' );
@@ -1494,6 +1605,35 @@ public function test_dismissing_hides_the_failed_files_until_one_fails_again() {
$this->assertSame( [ 'FreeSans.ttf' ], $this->new_downloader()->get_undismissed_failed_files() );
}
+ public function test_a_font_on_disk_clears_its_give_up_for_every_reader() {
+ $this->list_fixture_as( 'Chewy.ttf', self::FONT );
+ $this->copy_fixture( 'Chewy.ttf', self::FONT );
+ $this->set_font_failures(
+ [
+ self::FONT => $this->failure( time() - 10 ),
+ 'Garuda.ttf' => $this->failure( time() - 10 ),
+ ]
+ );
+ $this->set_back_off( self::FONT );
+
+ $downloader = $this->new_downloader();
+
+ $this->assertSame( [ 'Garuda.ttf' ], array_keys( $downloader->get_failed_files() ) );
+ $this->assertSame( [ 'Garuda.ttf' ], $downloader->get_undismissed_failed_files() );
+ $this->assertSame( [ 'Garuda.ttf' ], array_keys( $this->get_font_failures() ) );
+ $this->assertFalse( $this->get_back_off( self::FONT ) );
+ }
+
+ public function test_a_font_on_disk_keeps_a_retry_still_to_come() {
+ $this->list_fixture_as( 'Chewy.ttf', self::FONT );
+ $this->copy_fixture( 'Chewy.ttf', self::FONT );
+ $this->set_font_failures( [ self::FONT => $this->failure() ] );
+
+ $this->new_downloader()->get_failed_files();
+
+ $this->assertSame( [ self::FONT ], array_keys( $this->get_font_failures() ) );
+ }
+
public function test_only_a_true_dismissal_counts() {
$this->set_font_failures( [ self::FONT => [ 'retry_at' => 0, 'gave_up' => time(), 'dismissed' => 'yes' ] ] );
diff --git a/tests/phpunit/integration/Model/Test_Model_Font_Sources.php b/tests/phpunit/integration/Model/Test_Model_Font_Sources.php
index ce381e7a1..8cac6882a 100644
--- a/tests/phpunit/integration/Model/Test_Model_Font_Sources.php
+++ b/tests/phpunit/integration/Model/Test_Model_Font_Sources.php
@@ -1049,6 +1049,50 @@ public function test_a_family_uses_the_hashed_name_of_a_file_a_font_manager_reco
$this->assertTrue( $this->model->is_core_font_file( $hashed ) );
}
+ public function test_a_collision_builds_the_aliased_manifest_once_until_the_collisions_change() {
+ $model = new class( GPDFAPI::get_data_class(), $this->custom_fonts, GPDFAPI::get_log_class() ) extends Model_Font_Sources {
+ /** @var int */
+ public $built = 0;
+
+ protected function with_record_aliases( array $manifest ): array {
+ $this->built++;
+
+ return parent::with_record_aliases( $manifest );
+ }
+ };
+
+ $this->put_longer_font_at( 'DejaVuSans.ttf', 'DejaVuSans.ttf' );
+ $this->add_record( 'mine', [ 'regular' => 'DejaVuSans.ttf' ] );
+ $hashed = $model->get_hashed_name( 'DejaVuSans.ttf' );
+
+ $model->is_installed( 'DejaVuSans.ttf' );
+ $this->assertArrayHasKey( $hashed, $model->get_manifest() );
+ $this->assertSame( 1, $model->built );
+
+ GPDFAPI::delete_plugin_option( 'custom_fonts' );
+ $model->flush();
+
+ $this->assertArrayNotHasKey( $hashed, $model->get_manifest() );
+ }
+
+ public function test_the_manifest_has_every_collision_not_those_found_first() {
+ $this->put_longer_font_at( 'DejaVuSans.ttf', 'DejaVuSans.ttf' );
+ $this->put_longer_font_at( 'DejaVuSans-Bold.ttf', 'DejaVuSans-Bold.ttf' );
+ $this->add_record(
+ 'mine',
+ [
+ 'regular' => 'DejaVuSans.ttf',
+ 'bold' => 'DejaVuSans-Bold.ttf',
+ ]
+ );
+
+ $manifest = $this->model->get_manifest();
+
+ $this->assertCount( 2, $this->model->get_record_collisions() );
+ $this->assertArrayHasKey( $this->model->get_record_collisions()['DejaVuSans.ttf'], $manifest );
+ $this->assertArrayHasKey( $this->model->get_record_collisions()['DejaVuSans-Bold.ttf'], $manifest );
+ }
+
public function test_a_record_holding_a_known_build_of_the_core_font_is_not_custom() {
$this->copy_fixture( 'DejaVuSans.ttf' );
$this->add_record( 'mine', [ 'regular' => 'DejaVuSans.ttf' ] );
diff --git a/tests/playwright/core/system-status/core-fonts.spec.ts b/tests/playwright/core/system-status/core-fonts.spec.ts
index 0dea23ce7..f1ec3a4ca 100644
--- a/tests/playwright/core/system-status/core-fonts.spec.ts
+++ b/tests/playwright/core/system-status/core-fonts.spec.ts
@@ -40,6 +40,10 @@ test.describe('Core Fonts', () => {
'arimo, gfpdf-dejavu-symbols, dejavusanscondensed, freesans, garuda, khmeros, padaukbook, kaputaunicode, abyssinicasil'
);
+ await expect(
+ page.getByRole('row', { name: /^Core Font downloads/ })
+ ).toContainText('Off (gfpdf_auto_install_fonts filter)');
+
await snapshot(page, testinfo, [installed, substitution], {
mask: [installed.locator('td').last()],
});
diff --git a/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-failed-downloads-should-raise-a-dismissible-notice-listing-each-font-that-failed-to-download.png b/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-failed-downloads-should-raise-a-dismissible-notice-listing-each-font-that-failed-to-download.png
index 09878aeda..8a19056c7 100644
Binary files a/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-failed-downloads-should-raise-a-dismissible-notice-listing-each-font-that-failed-to-download.png and b/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-failed-downloads-should-raise-a-dismissible-notice-listing-each-font-that-failed-to-download.png differ
diff --git a/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-should-report-the-installed-core-fonts-and-substitution-fonts-in-the-system-report.png b/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-should-report-the-installed-core-fonts-and-substitution-fonts-in-the-system-report.png
index b60ca1ff6..7a7ff33c3 100644
Binary files a/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-should-report-the-installed-core-fonts-and-substitution-fonts-in-the-system-report.png and b/tests/playwright/visual-baselines/core-core-system-status-core-fonts-core-fonts-should-report-the-installed-core-fonts-and-substitution-fonts-in-the-system-report.png differ
diff --git a/tests/playwright/visual-baselines/multisite-multisite-failed-fonts-notice-failed-core-fonts-notice-on-a-multisite-should-list-the-fonts-that-gave-up-on-every-site-and-in-the-network-admin.png b/tests/playwright/visual-baselines/multisite-multisite-failed-fonts-notice-failed-core-fonts-notice-on-a-multisite-should-list-the-fonts-that-gave-up-on-every-site-and-in-the-network-admin.png
index 09878aeda..8a19056c7 100644
Binary files a/tests/playwright/visual-baselines/multisite-multisite-failed-fonts-notice-failed-core-fonts-notice-on-a-multisite-should-list-the-fonts-that-gave-up-on-every-site-and-in-the-network-admin.png and b/tests/playwright/visual-baselines/multisite-multisite-failed-fonts-notice-failed-core-fonts-notice-on-a-multisite-should-list-the-fonts-that-gave-up-on-every-site-and-in-the-network-admin.png differ