Repository navigation
Let plugins add font sources through the gfpdf_font_sources filter - #2013
Closed
gravitypdf-bot wants to merge 4 commits into
Closed
gravitypdf-bot wants to merge 4 commits into
gravitypdf-bot wants to merge 4 commits into
Conversation
Contributor
|
Coverage report for commit: bbc0ca5 Summary - Lines: 93.26% | Methods: 87.50% | Branches: 83.03%
🤖 Jest coverage report |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
On-demand downloading makes deleting a core font safe, as one a PDF needs comes back on its own, but the Font Manager only listed custom fonts, so a downloaded font could only be removed by hand from the fonts folder. The Font Manager gets a "Downloaded Fonts" section listing each family of a downloadable source with files on disk: its styles, size and why it's in use (default font, site language, PDF count, a required source, another site, or a file another required font shares). In-use families can't be deleted; the server re-checks and returns 409, and 404 for a bundled, unknown or absent family. Delete removes the family's manifest files whatever their hash (an explicit action, unlike the upgrade prune), keeps the licence files, clears the files' failure and back-off entries, flushes the family's metrics and logs what it removed. With downloads turned off the confirm says the font won't come back. The in-use reasons come from Model_Required_Fonts::get_font_reasons(), which get_required_font_files() is now built on, so the list and the background sync share one walk of the forms. On Multisite the fonts folder is shared, so only super admins can delete, and "in use" also counts every other site's default font, locale font and PDF fonts. Those come from Model_Required_Fonts::get_site_font_keys(), which reads a site's tables by prefix rather than switch_to_blog() so no other site's plugins or filters run. One options query per site also says whether Gravity Forms has run there (gf_db_version), so no site pays a separate table check. Routes live in Controller_Font_Downloader, beside the font sync it already wires up, rather than Controller_Custom_Fonts, which the multisite-safe CRUD work also changes. The E2E test copies a core font no other spec uses into the fonts folder, as downloads are off on the E2E site, and puts one in use with an X-GPDF-E2E-Default-Font header rather than a PDF selecting it, which would count it as required in the other specs' system reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 of 6 tasks
…nts in the 6.19 cleanup Every site shares the fonts folder, so the font state about it is now stored the same way on single site and Multisite: the failed downloads record and the 6.19 cleanup's progress are main network options, the language font cache is a site transient, and the failed fonts notice's dismissal lives in the record rather than in each site's settings. The 6.19 cleanup keeps the fonts any site needs, reading each site's settings, locale and PDFs directly through the same reader the Font Manager uses for other sites' fonts. It runs in a cron event that reads the sites in batches within the sync's time budget, so no upgrade request pays for a large network's scan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts name
download() returned true for any file at a manifest name, so a family could silently use another font's file, and
merging two sources' manifests with += gave the later source the first's file for a name they use for different files.
A file at a manifest name is another font's when its size matches no build the sources know (the manifest's, the
legacy manifest's or a source's `legacy` hashes). The family's font is then saved as {stem}-{first 8 of the sha}{ext},
derived from the manifest with nothing stored, and mPDF looks for that name first. The file at the name is never
touched. A size mismatch is a stat call, so it is also found while rendering; a file of a known size that is not the
manifest's is found by the background sync, the only place a file already on disk is hashed. With downloads off, or a
download failing or backing off, the file at the name stays in use, so a conflict never renders worse than before.
Two sources that use one name for different files get it at merge time: the later source's manifest entry and family
file references take the hashed name, so nothing downstream sees a duplicate.
Every reader of a font file's path now agrees: the finder, the downloaded fonts list and delete (a hashed copy is
deleted alone), the installed counts and needs-download checks, the sync's file list and retries, prune and adoption
(which read the file at the name), and the unregistered fonts auto-loader (which skips hashed copies). Saving a hashed
copy flushes the font metrics and removes a copy left by an older release of the font.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gravitypdf-bot
force-pushed
the
feature/font-sources-filter
branch
from
November 6, 2026 00:35
7d9eed7 to
4d11a67
Compare
gravitypdf-bot
changed the base branch from
feature/core-font-upgrade-prune
to
feature/font-file-conflict-rename
November 6, 2026 00:35
A plugin (Gravity PDF Pro's template fonts, for one) can now register a font source: mPDF families whose files download from the source's own manifest through the same verified downloader as the core fonts, optionally listed in their own font list group and, when `required`, kept on disk by the background sync. - Each added source is validated once per request: a lowercase key ID that isn't `bundled` or `core`, families with a Regular file and plain file names, a readable manifest, and a label group, scalar version and SHA-1 => size legacy if set. An invalid one is logged and dropped; only the known keys are kept, so an added source can't set a `dir`. - Building the sources reads each downloadable source's manifest, since two sources using one file name for different files must be told apart (the later source's file takes its hashed name, as for the core fonts). Only valid sources get that far, so a malformed source can't break the rename. An added source's manifest is all or nothing: one malformed entry (e.g. an http:// URL) or a family file missing from it rejects the whole file, logged, so a bad build never half-installs. Its families stay registered and render through the on-demand finder's fallback; nothing of it downloads. Dropping the source instead would have made the sources depend on a manifest's contents. - On admin_init, each added source that's new or has a new `version` (option `gfpdf_font_sources`, per site, since custom font records are per site) adopts its records, then `gfpdf_font_sources_changed` fires and the downloader schedules a sync. This covers activation by any route (UI, network, WP-CLI, file copy) and an update shipping new fonts. A removed source is forgotten, so adding it back adopts again; one with a rejected manifest stays unrecorded until fixed. The option is stored even when empty so later reads come from the autoloaded options, and the check returns early when nothing hooks the filter and nothing is stored. - `GPDFAPI::add_pdf_font()` and the required set already loop over the registered sources, so they cover added sources unchanged; adoption now reuses the cached source manifest instead of reading the file again. - The option is deleted on uninstall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gravitypdf-bot
force-pushed
the
feature/font-sources-filter
branch
from
November 6, 2026 01:50
4d11a67 to
bbc0ca5
Compare
Contributor
|
Coverage report for commit: bbc0ca5 Summary - Lines: 89.48% | Methods: 95.74%
🤖 PHPUnit coverage report |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jakejackson1
force-pushed
the
feature/font-file-conflict-rename
branch
from
November 6, 2026 03:06
f7acf3e to
74c3b06
Compare
gravitypdf-bot
force-pushed
the
feature/font-file-conflict-rename
branch
3 times, most recently
from
November 9, 2026 06:09
62cbe50 to
38e02af
Compare
Base automatically changed from
feature/font-file-conflict-rename
to
development
November 9, 2026 06:12
Member
|
Not planned. Include additional fonts in the current manifest. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Plugins can now add their own font sources through a new
gfpdf_font_sourcesfilter. This is how Gravity PDF Pro will serve the premium template fonts. A source's families register with mPDF and can be listed in their own font list group. Their files download from the source's manifest through the same verified downloader as the core fonts, and when the source isrequired, the background sync keeps them on disk. When a source is added or itsversionchanges, the next admin request adopts any matching custom font records and schedules a sync. Stacked on #2016.Model_Font_Sources::get_sources()applies the filter once per request. It validates each added source, logs and drops any that are invalid, then runs Save a core font under a hashed name when another font's file holds its name #2016'srename_colliding_files()over the valid ones. ThebundledandcoreIDs are reserved.http://URL), or a family file missing from the manifest, rejects the whole file and logs it.check_added_sources()runs onadmin_initand stores each source's version in the per-site optiongfpdf_font_sources. When a source is new or its version changed, it adopts the source's records and firesgfpdf_font_sources_changed, whichController_Font_Downloaderhooks toschedule_sync().GPDFAPI::add_pdf_font()needed no changes: it's a no-op for any family key a registered source provides. The required set also counts addedrequiredsources.Try it
gfpdf_font_sources. Give it afamiliesentry such as'lato' => [ 'name' => 'Lato', 'R' => 'Lato-Regular.ttf' ], amanifestPHP file returning[ 'Lato-Regular.ttf' => [ 'url' => 'https://…', 'size' => …, 'sha' => '<git blob SHA-1>' ] ], plus'group' => 'Template Fonts','required' => trueand'version' => '1.0.0'.wp option get gfpdf_font_sourcesshows{"…": "1.0.0"}, andwp cron event listshowsgfpdf_core_font_sync.wp-content/uploads/PDF_EXTENDED_TEMPLATES/fonts/.http://, bumpversionand reload an admin page. The log shows "Font manifest rejected: an entry is invalid" and nothing downloads.Test plan
Test_Added_Font_Sources(new) covers these cases:http://URL or a missing family file is rejected wholempdf_font_dataregisters the source before custom records, so a custom record still overrides itfontDiradd_pdf_font()is a no-op only while the source is registeredrequiredsource's files are in the required set, and a non-required source's files aren'tTest_Model_Font_Sources,Test_Model_Required_Fonts,Test_Model_Font_Downloader,Test_Downloaded_Fonts,Test_Api,Test_Controller_Upgrade_Routines,Test_Public_Api_Contract, Uninstall,Test_Controller_System_Report,Test_Controller_Actions,Test_On_Demand_Font_File_Finder,Test_Font_Rendering. The only failures are environment-only:test_an_unwritable_folder_fails_without_a_request×2 (the container runs as root) andtest_the_kill_switch_constant_turns_downloads_off(the parallel-harness constant warning).More info
Validation. An added source is checked once, when the sources are built, and must have:
sanitize_key()-clean IDfamilies, each keyed by a lowercase string, with a Regular file, and every R/B/I/BI value a plain file namemanifestIf set,
groupmust be a non-empty string,versiona scalar, andlegacya map of git blob SHA-1 => positive int.Only
families,manifest,group,required,legacyandversionare kept, so an added source can't set adirorlegacy_manifest.A filter that rewrites or unsets
bundled/coregets the originals back; a rewrite is also logged.Rebased onto #2016. The sources now always read the manifests of the downloadable sources when they are built (an added source plus
coreis two), because file names two sources share have to be told apart. Validation runs first, sorename_colliding_files()only sees well-formed sources, and it reads each manifest through the same all-or-nothing check, so a rejected manifest takes no file name. After the filter,get_sources()clears the manifest, family-file and record-collision caches, in case a callback read them from the core sources alone, andget_manifest(),get_record_collisions()andget_record_file_names()build the sources before touching those caches.Manifest rejection, not drop. The plan's test list says a source with a bad manifest is "dropped". Dropping it when the manifest is first read would make
get_sources()change partway through a request, depending on whether anything had read that manifest yet. Instead,read_source_manifest()rejects the whole file, and checks that every family file is on it. A rejected source keeps its families registered, but none of its files are downloadable, required, or adoptable. Its PDFs render through the on-demand finder's fallback. A source whose manifest was rejected isn't recorded ingfpdf_font_sources, so it's adopted and synced once fixed. Core manifests keep the existing skip-and-warn behaviour.Version option. Plan §2 had superseded the stored-version design for
bundled/core, which still adopt in the 6.19 upgrade routine. Added sources need it, since a plugin activated by any route has no upgrade routine of ours to run, so only added sources are tracked. The option is per site because custom font records are. It is stored even when empty, autoloaded, so later reads come from the alloptions cache instead of a query per admin request.check_added_sources()returns early when the stored value is[]and nothing hooks the filter.Refactors.
adopt_records()now reuses the cachedget_source_manifest()instead of re-including the file.is_blob()andis_file_name()are shared by manifest entries, legacy hashes and family files. The tests' manifest writer moved intoCopiesFontFixtures::write_manifest_file().🤖 Generated with Claude Code