Skip to content

Add PDF Download endpoint#1568

Merged
jakejackson1 merged 1 commit into
developmentfrom
rest-download-pdf
Jun 5, 2026
Merged

Add PDF Download endpoint#1568
jakejackson1 merged 1 commit into
developmentfrom
rest-download-pdf

Conversation

@jakejackson1

@jakejackson1 jakejackson1 commented Aug 30, 2024

Copy link
Copy Markdown
Member

Description

Adds a new REST endpoint that generates an entry's PDF on demand. The document can be returned inline (base64) or as a short-lived signed URL that streams the file — so integrations can fetch a finished PDF without going through the browser-based download links.

Endpoint

POST gravity-pdf/v1/download/<entry>/<pdf>
GET  gravity-pdf/v1/download/<entry>/<pdf>   (signed-URL proxy — see below)
  • <entry> — the Gravity Forms entry ID (validated against the form database).
  • <pdf> — the 13-character PDF ID (validated against the entry's active PDFs).

POST — request the PDF

Body parameters:

Param Type Default Notes
type base64 | url base64 How to return the document.
urlExpiry string global timeout Only used for type=url. A relative time string ("30 minutes", "2 hours"). Falls back to the global signed-URL timeout (logged_out_timeout) when omitted or invalid.

type=base64 (default) returns the document inline:

{ "filename": "example.pdf", "size": 12345, "data": "<base64-encoded PDF>" }

type=url generates and caches the PDF, then returns a signed proxy URL instead of the bytes:

{ "filename": "example.pdf", "size": 12345, "url": "https://site/wp-json/gravity-pdf/v1/download/<entry>/<pdf>?expires=…&signature=…" }

The response also includes HAL _links (self, pdf, and — when the Gravity Forms REST API v2 is enabled — direct form and entry links).

GET — the signed-URL proxy

The url returned above points back at the same route via GET. The signature acts as the bearer token: the request is authorised purely by verifying the signature and expiry (reusing the same Helper_Url_Signer infrastructure as Gravity PDF's front-end signed PDF links), then the PDF is streamed to the browser as a download. No login is required, which is what makes the URL shareable.

Access control (POST)

A request is authorised if either:

  1. The current user has the capability to view PDFs, or
  2. The current user is logged in, owns the entry (created_by), and owner access has not been disabled via the PDF's "Restrict Owner" setting.

Otherwise the request is rejected.

Supporting changes

  • Rest_Form_Settings — exposes the id, form, active and per-group setting fields in the embed context as well as edit, so the new endpoint's embedded pdf link returns useful data. Also switches a couple of direct \GFAPI:: calls to the injected $this->gform helper for consistency and testability.
  • Rest_Pdf_Preview — same \GFAPI::$this->gform swap.
  • Model_PDF::set_watermark_font() — falls back to the global default font when neither a watermark font nor a form font is set, instead of triggering an "undefined array key" warning during PDF generation.
  • bootstrap.php — registers the new Rest_Download_Pdf controller (now also wired with Helper_Url_Signer).

Testing instructions

  1. Create a form with at least one active PDF and submit an entry.
  2. base64: As a user with PDF-view capability, POST to gravity-pdf/v1/download/<entry>/<pdf> and confirm a JSON payload whose decoded data begins with %PDF-.
  3. url: POST the same route with {"type":"url"} and confirm a url is returned containing expires and signature. Open it in a browser and confirm the PDF downloads. Wait past the expiry (or pass a short urlExpiry like "1 second") and confirm the link is then rejected.
  4. As the entry owner (non-admin), confirm POST succeeds — and fails once the PDF's "Restrict Owner" option is enabled.
  5. As an anonymous / unrelated user, confirm POST is rejected, and that a tampered/expired signed URL is rejected on GET.
  6. Invalid entry or PDF IDs should return a 400 validation error.

Automated coverage lives in tests/phpunit/integration/Rest/Test_Rest_Download_Pdf.php (route registration, the permission matrix, base64 + url generation, and signed-URL verification).

Checklist:

  • I've tested the code.
  • My code is easy to read, follow, and understand
  • My code follows the accessibility standards.
  • My code has proper inline documentation / docblocks.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Coverage report for commit: 9c3aae0
File: ./tmp/jest-coverage/clover.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  10% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  20% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  30% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  40% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  50% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  60% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  70% │ █░░░░░░░░░░░░░░░░░░░░░░ │  1.6%
  80% │ ███░░░░░░░░░░░░░░░░░░░░ │  8.2%
  90% │ ████████░░░░░░░░░░░░░░░ │ 21.3%
 100% │ ███████████████████████ │ 68.9%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 92.96% | Methods: 88.24% | Branches: 81.37%
FilesLinesMethodsBranches
src/assets/js/react/actions
   coreFonts.js100.00%100.00%100.00%
   fontManager.js100.00%100.00%100.00%
   templates.js100.00%100.00%100.00%
src/assets/js/react/components/Alert
   Alert.js100.00%100.00%100.00%
src/assets/js/react/components/CoreFonts
   CoreFontContainer.js100.00%100.00%91.43%
   CoreFontCounter.js100.00%100.00%100.00%
   CoreFontListResults.js100.00%100.00%85.71%
   CoreFontListSpacer.js100.00%100.00%100.00%
src/assets/js/react/components
   Empty.js100.00%100.00%100.00%
   ShowMessage.js79.31%80.00%64.29%
   Spinner.js100.00%100.00%100.00%
src/assets/js/react/components/FontManager
   AddFont.js100.00%100.00%100.00%
   AddUpdateFontFooter.js85.37%50.00%88.89%
   AdvancedButton.js100.00%100.00%100.00%
   FontList.js100.00%50.00%65.22%
   FontListAlertMessage.js100.00%100.00%100.00%
   FontListHeader.js100.00%100.00%100.00%
   FontListIcon.js100.00%100.00%100.00%
   FontListItems.js85.39%64.00%68.66%
   FontListSkeleton.js100.00%100.00%100.00%
   FontManager.js77.78%57.14%50.00%
   FontManagerBody.js94.20%96.43%90.29%
   FontManagerHeader.js100.00%100.00%100.00%
   FontVariant.js90.00%60.00%70.00%
   FontVariantLabel.js100.00%100.00%100.00%
   InitialAddUpdateState.js100.00%100.00%100.00%
   SearchBox.js90.00%66.67%69.23%
   TemplateTooltip.js100.00%75.00%100.00%
   UpdateFont.js75.00%50.00%75.00%
src/assets/js/react/components/Modal
   CloseDialog.js93.33%66.67%70.59%
src/assets/js/react/components/Template
   TemplateActivateButton.js100.00%100.00%100.00%
   TemplateButton.js85.71%66.67%100.00%
   TemplateContainer.js71.43%66.67%25.00%
   TemplateDeleteButton.js100.00%100.00%70.00%
   TemplateFooterActions.js100.00%100.00%100.00%
   TemplateHeaderNavigation.js82.35%85.71%70.00%
   TemplateHeaderTitle.js100.00%100.00%100.00%
   TemplateList.js100.00%100.00%60.00%
   TemplateListItem.js100.00%100.00%92.86%
   TemplateListItemComponents.js100.00%100.00%100.00%
   TemplateScreenshot.js100.00%100.00%100.00%
   TemplateScreenshots.js100.00%100.00%50.00%
   TemplateSearch.js93.75%88.89%50.00%
   TemplateSingle.js100.00%100.00%100.00%
   TemplateSingleComponents.js100.00%100.00%75.00%
   TemplateUploader.js98.04%100.00%86.67%
src/assets/js/react/reducers
   coreFontReducer.js95.65%100.00%88.00%
   fontManagerReducer.js87.21%75.00%75.00%
   index.js100.00%100.00%100.00%
   templateReducer.js100.00%100.00%100.00%
src/assets/js/react/sagas
   coreFonts.js91.67%100.00%75.00%
   fontManager.js86.96%90.00%83.33%
   index.js100.00%100.00%100.00%
   templates.js83.33%100.00%100.00%
src/assets/js/react/selectors
   getTemplates.js91.11%100.00%83.33%
src/assets/js/react/utilities/FontManager
   adjustFontListHeight.js100.00%100.00%100.00%
   associatedFontManagerSelectBox.js94.44%100.00%66.67%
   fontManagerReducer.js100.00%100.00%100.00%
   getTabLocation.js100.00%100.00%100.00%
   toggleUpdateFont.js100.00%100.00%100.00%
src/assets/js/react/utilities
   withRouterHooks.js100.00%100.00%100.00%

🤖 Jest coverage report

@jakejackson1 jakejackson1 force-pushed the rest-download-pdf branch 2 times, most recently from 1c1b36f to c212b92 Compare June 4, 2026 03:02
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Coverage report for commit: 9c3aae0
File: tmp/coverage/report-xml/merged.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ██████████░░░░░░░░░░░░░ │ 11.5%
  10% │ █░░░░░░░░░░░░░░░░░░░░░░ │  1.0%
  20% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.5%
  30% │ ██░░░░░░░░░░░░░░░░░░░░░ │  1.4%
  40% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.5%
  50% │ ███████░░░░░░░░░░░░░░░░ │  8.1%
  60% │ ██░░░░░░░░░░░░░░░░░░░░░ │  1.4%
  70% │ ███████░░░░░░░░░░░░░░░░ │  7.7%
  80% │ ████████████████░░░░░░░ │ 19.6%
  90% │ ███████████████████████ │ 29.2%
 100% │ ████████████████░░░░░░░ │ 19.1%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 82.29% | Methods: 88.67%
FilesLinesMethodsBranches
/var/www/html/wp-content/plugins/gravity-pdf
   api.php96.24%100.00%100.00%
   gravity-pdf-updater.php27.50%100.00%100.00%
   pdf.php60.34%87.50%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Controller
   Controller_Actions.php100.00%100.00%100.00%
   Controller_Activation.php95.45%100.00%100.00%
   Controller_Custom_Fonts.php88.52%100.00%100.00%
   Controller_Debug.php100.00%100.00%100.00%
   Controller_Export_Entries.php96.67%100.00%100.00%
   Controller_Form_Settings.php86.05%90.00%100.00%
   Controller_Install.php100.00%100.00%100.00%
   Controller_Mergetags.php100.00%100.00%100.00%
   Controller_PDF.php82.05%100.00%100.00%
   Controller_Pdf_Queue.php83.72%77.78%100.00%
   Controller_Save_Core_Fonts.php66.67%100.00%100.00%
   Controller_Settings.php80.23%100.00%100.00%
   Controller_Shortcodes.php100.00%100.00%100.00%
   Controller_System_Report.php100.00%100.00%100.00%
   Controller_Templates.php100.00%100.00%100.00%
   Controller_Uninstaller.php83.33%77.78%100.00%
   Controller_Upgrade_Routines.php93.33%100.00%100.00%
   Controller_Webhooks.php100.00%100.00%100.00%
   Controller_Zapier.php100.00%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Exceptions
   GravityPdfDatabaseUpdateException.php50.00%100.00%100.00%
   GravityPdfDomainException.php50.00%100.00%100.00%
   GravityPdfException.php50.00%100.00%100.00%
   GravityPdfFontNotFoundException.php50.00%100.00%100.00%
   GravityPdfIdException.php50.00%100.00%100.00%
   GravityPdfModelNotUpdatedException.php50.00%100.00%100.00%
   GravityPdfRuntimeException.php50.00%100.00%100.00%
   GravityPdfShortcodeEntryIdException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfConditionalLogicFailedException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfConfigNotFoundException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfInactiveException.php50.00%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Fields
   Field_Address.php92.16%100.00%100.00%
   Field_Chainedselect.php66.67%75.00%100.00%
   Field_Checkbox.php94.34%100.00%100.00%
   Field_Consent.php89.47%100.00%100.00%
   Field_Coupon.php--100.00%
   Field_Creditcard.php83.33%100.00%100.00%
   Field_Date.php83.33%100.00%100.00%
   Field_Default.php83.33%100.00%100.00%
   Field_Discount.php44.00%75.00%100.00%
   Field_Email.php83.33%100.00%100.00%
   Field_Fg_Ls_Consent.php92.86%100.00%100.00%
   Field_Fg_Ls_Signature.php73.08%66.67%100.00%
   Field_Fileupload.php94.23%100.00%100.00%
   Field_Form.php89.09%100.00%100.00%
   Field_Hidden.php81.82%100.00%100.00%
   Field_Html.php89.47%100.00%100.00%
   Field_Image_Choice.php86.67%100.00%100.00%
   Field_Likert.php97.22%100.00%100.00%
   Field_List.php92.41%100.00%100.00%
   Field_Multi_Choice.php50.00%100.00%100.00%
   Field_Multiselect.php92.59%100.00%100.00%
   Field_Name.php84.62%100.00%100.00%
   Field_Number.php83.33%100.00%100.00%
   Field_Option.php57.69%50.00%100.00%
   Field_Page.php83.33%100.00%100.00%
   Field_Phone.php81.82%100.00%100.00%
   Field_Poll.php93.75%100.00%100.00%
   Field_Post_Category.php85.00%100.00%100.00%
   Field_Post_Content.php82.35%100.00%100.00%
   Field_Post_Custom_Field.php50.00%100.00%100.00%
   Field_Post_Excerpt.php81.82%100.00%100.00%
   Field_Post_Image.php94.00%100.00%100.00%
   Field_Post_Tags.php90.91%100.00%100.00%
   Field_Post_Title.php81.82%100.00%100.00%
   Field_Product.php88.46%100.00%100.00%
   Field_Products.php85.41%100.00%100.00%
   Field_Quantity.php84.62%100.00%100.00%
   Field_Quiz.php89.74%100.00%100.00%
   Field_Radio.php95.35%100.00%100.00%
   Field_Rank.php95.24%100.00%100.00%
   Field_Rating.php95.24%100.00%100.00%
   Field_Repeater.php97.37%100.00%100.00%
   Field_Section.php90.74%100.00%100.00%
   Field_Select.php94.12%100.00%100.00%
   Field_Shipping.php75.00%66.67%100.00%
   Field_Signature.php67.39%100.00%100.00%
   Field_Slim.php82.35%100.00%100.00%
   Field_Slim_Post.php91.30%100.00%100.00%
   Field_Subtotal.php65.38%75.00%100.00%
   Field_Survey.php95.00%100.00%100.00%
   Field_Tax.php32.00%50.00%100.00%
   Field_Text.php81.82%100.00%100.00%
   Field_Textarea.php90.63%100.00%100.00%
   Field_Time.php81.82%100.00%100.00%
   Field_Tos.php92.59%100.00%100.00%
   Field_Total.php68.00%66.67%100.00%
   Field_V3_List.php92.86%100.00%100.00%
   Field_V3_Products.php73.68%100.00%100.00%
   Field_V3_Section.php77.78%100.00%100.00%
   Field_Website.php85.71%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Fonts
   FlushCache.php80.00%100.00%100.00%
   LocalFile.php90.00%100.00%100.00%
   LocalFilesystem.php66.67%100.00%100.00%
   SupportsOtl.php88.89%100.00%100.00%
   TtfFontValidation.php72.73%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper
   Helper_Abstract_Addon.php92.14%100.00%100.00%
   Helper_Abstract_Config_Settings.php75.00%100.00%100.00%
   Helper_Abstract_Controller.php-100.00%100.00%
   Helper_Abstract_Field_Products.php89.47%66.67%100.00%
   Helper_Abstract_Fields.php93.59%93.75%100.00%
   Helper_Abstract_Fields_Input_Type.php84.00%100.00%100.00%
   Helper_Abstract_Form.php--100.00%
   Helper_Abstract_Model.php100.00%100.00%100.00%
   Helper_Abstract_Options.php74.73%75.00%100.00%
   Helper_Abstract_Pdf_Shortcode.php89.52%91.67%100.00%
   Helper_Abstract_View.php95.83%100.00%100.00%
   Helper_Data.php96.57%100.00%100.00%
   Helper_Field_Container.php92.31%100.00%100.00%
   Helper_Field_Container_Gf25.php82.22%100.00%100.00%
   Helper_Field_Container_Void.php16.67%-100.00%
   Helper_Form.php70.97%64.29%100.00%
   Helper_Interface_Actions.php-100.00%100.00%
   Helper_Interface_Config.php50.00%100.00%100.00%
   Helper_Interface_Config_Settings.php50.00%100.00%100.00%
   Helper_Interface_Extension_Settings.php-100.00%100.00%
   Helper_Interface_Extension_Uninstaller.php-100.00%100.00%
   Helper_Interface_Field_Pdf_Config.php50.00%100.00%100.00%
   Helper_Interface_Filters.php-100.00%100.00%
   Helper_Interface_Setup_TearDown.php-100.00%100.00%
   Helper_Interface_Url_Signer.php-100.00%100.00%
   Helper_Logger.php-100.00%100.00%
   Helper_Misc.php73.12%90.63%100.00%
   Helper_Mpdf.php50.00%100.00%100.00%
   Helper_Notices.php79.31%78.57%100.00%
   Helper_Options_Fields.php96.41%94.12%100.00%
   Helper_PDF.php89.66%94.29%100.00%
   Helper_PDF_List_Table.php84.50%92.31%100.00%
   Helper_Pdf_Queue.php84.31%100.00%100.00%
   Helper_QueryPath.php80.00%100.00%100.00%
   Helper_Sha256_Url_Signer.php87.50%100.00%100.00%
   Helper_Singleton.php90.00%100.00%100.00%
   Helper_Templates.php96.43%100.00%100.00%
   Helper_Trait_Logger.php--100.00%
   Helper_Url_Signer.php82.86%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Licensing
   EDD_SL_Plugin_Updater.php96.64%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Log
   Logger.php70.10%100.00%100.00%
   MonoLoggerPsrLog2And3.php--100.00%
   Redact_Processor.php100.00%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Mpdf
   Cache.php66.67%100.00%100.00%
Table truncated to fit comment

🤖 PHPUnit coverage report

@GravityPDF GravityPDF deleted a comment from codecov Bot Jun 4, 2026
@jakejackson1 jakejackson1 force-pushed the rest-download-pdf branch 4 times, most recently from 1c06676 to f58e5ad Compare June 4, 2026 06:15
@jakejackson1 jakejackson1 marked this pull request as ready for review June 4, 2026 06:18
@jakejackson1 jakejackson1 force-pushed the rest-download-pdf branch 3 times, most recently from 6fddabc to 614b312 Compare June 5, 2026 01:02
Add a REST endpoint (gravity-pdf/v1/download/<entry>/<pdf>) that generates
and returns a PDF for a Gravity Forms entry as base64-encoded data, with
capability- and entry-owner-based access control and HAL links. Wire the
controller into the Router and add PHPUnit coverage for route registration,
permissions (anonymous, non-owner, owner, restrict_owner) and generation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jakejackson1 jakejackson1 merged commit 972cd6e into development Jun 5, 2026
17 of 18 checks passed
@jakejackson1 jakejackson1 deleted the rest-download-pdf branch June 5, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant