I needed FillPDF generated PDFs to be stored in a file field on the node they were generated for as part of a project (feature discussed here previously: https://www.drupal.org/node/1606828).
I have developed a FillPDF field sub-module which provides this functionality, only for nodes at the moment.
Upload the files into your fillpdf/modules folder and enable. You can now add a field of type 'Fill PDF' to any node. In the field settings you can specify:
- whether the generated PDF will be stored as a public or a private file
In the instance settings you can specify:
- the default Fill PDF form to use
- if you want to allow selection of the form at the point of generation
- if so, which forms should be allowed
Once added to a node, the field will not show anything on the node edit form. When viewing the node it will show a table of generated PDFs (with remove buttons) and a button to generate further PDFs within the cardinality limit.
Module should work with 7.x-2.x-dev and 7.x-1.9.
Any feedback/improvements/testing welcome.
| Comment | File | Size | Author |
|---|---|---|---|
| #57 | d7_fillpdf_field_module_53-57_interdiff.txt | 3.83 KB | pancho |
| #57 | d7_fillpdf_field_module_57.patch | 58.23 KB | pancho |
| #53 | d7_fillpdf_field_module_52-53_interdiff.txt | 282 bytes | pancho |
| #53 | d7_fillpdf_field_module_53.patch | 62.16 KB | pancho |
| #52 | d7_fillpdf_field_module_51-52_interdiff.txt | 3.26 KB | pancho |
Comments
Comment #1
wizonesolutionsThis looks fantastic. Thanks for contributing it.
I don't see much that needs to be changed, aside from it being tested. My main comments are:
1) Fill PDF -> FillPDF (I changed the spacing of the module name a while ago since people were invariably calling it that anyway)
2) Watch out for extraneous whitespace at the beginning of lines. Saw a good amount of that while I was looking over the files.
I think this would make a good submodule for FillPDF, and when it gets to the point we commit it, it will obviously have proper attribution to your d.o username :)
Comment #2
wizonesolutionsBumping to 8.x-4.x-dev. The submodule in this issue is for 7.x-2.x. My thinking though is to port it conceptually to Drupal 8 first, then backport it or just fix up/incorporate the submodule.
Comment #3
panchoI'm very interested in this awesome concept and will work on a D8 port. I think this should be doable in the 8.x-4.x branch, but otherwise in 8.x-5.x. We'll then backport the final implementation to D7.
Comment #4
panchoFirst observations testing the D7 version:
1.)
Both the dropdown to select a FillPDFForm and the checkboxes show empty, unidentifiable entries for FillPdfForms that don't have an admin_title. Looks a bit like the D8 screenshots in #3022485-4: Undefined entity label impedes identifying FillPdfForm.
We should really get #3041029: [PP-2] Slightly less optional 'admin_title' into 7.x-1.x now, and at a later point get #3040776: Autocreate 'admin_title' from metadata and mark it required into 7.x-3.x.
2.)
fillpdf_field already gets right what fillpdf 7.x-1.x gets semi-wrong: it already takes care about #3040903: Offer all available file schemes, but only those, preselecting the site default. Another candidate for backport, once committed in 8.x-4.x.
Comment #5
panchoLet's do it this way:
I'm starting with 1.), the original, unchanged D7 version as provided by @steveaps in the original post. Here we go.
Comment #6
panchoThere's been a whole lot of errors, warnings and notices:
but in the end, I had to fix just a few lines to get it all running. A very nice contribution!
Note that I only fixed errors/warnings/notices, some coding style and case style here, so anything beyond that still needs to be done.
To make sure the patch is easy to review in spite of the coding style corrections, I'm adding an "interdiff -wiB", which ignores most whitespace, blank lines and case.
Comment #7
panchoCouple of minor things:
This can go:
Concatenating two integers without an underscore looks a bit messy:
Needs proper replacement:
+ many missing t() calls
Comment #8
panchoAlso, this is no good idea: the single allowed form gets deleted, suddenly none would be allowed, but in fact all are allowed.
That's why this otherwise user-friendly pattern was removed from all Core modules.
+ some more code style fixes.
Quite nice now & ready for usability testing!
Comment #9
panchoWe definitely need to think about user permissions here. Currently every anonymous user who may view published content, may also generate and/or delete PDFs. IMO this needs to be restricted to users with edit permission for the host entity. But we might need to be even more fine-grained.
Basically I'm unsure about the use case for the current implementation. I'm sure it perfectly covers what @steveaps needed for his project. But a FillPDF field may be done in very different ways to cover different use cases. I'm asking myself whether this is one we want to promote, whether this is the way to go.
Is this the 70% use case? Maybe we need to figure out and collect all kinds of use cases first, and then see how these might be best accomplished, before putting more work into porting it to D8.
Please everybody: I need testers and some feedback! :)
Comment #10
panchoI'm porting it to D8 anyway. We may then better see if and what needs to be changed/added/brought in line with Core patterns/made configurable.
Use cases:
A1.) Open service
I want users to be able to populate a FillPdf Form right from the entity.
Whoever may view the entity, should also be allowed to create and delete any generated PDF files right from the entity view. There should be a 'Create' button to do so. Already created PDFs should be displayed as a field there, too, together with a 'Delete' button so they can be deleted again. ✅
A2.) Self-service
I want users to be able to populate a FillPdf Form right from the entity.
Whoever may edit the entity, should also be allowed to create and delete any generated PDF files right from the entity view. There should be a 'Create' button to do so. Already created PDFs should be displayed as a field there, too, together with a 'Delete' button so they can be deleted again.
A3.) Admin removal
I want users to be able to populate a FillPdf Form right from the entity.
Whoever may edit the entity, should also be allowed to create a populated PDF file right from the entity view. There should be a 'Create' button to do so. Already created PDFs should be displayed as a field there, too. As an admin, I want to be able to delete populated PDFs right from there, too.
B1.) Tickbox on entity form
I want users to be able to populate a FillPdf Form right from the entity.
Whoever may edit the entity, should also be allowed to create and delete any generated PDF files. On the entity form, there should be a tick box to create a new populated PDF file. On the entity view, the last created FillPdfForm should be displayed as a field and should have a 'Delete' button so they can be deleted again.
B2.) Semiautomatic, privileged user decides on or off
I want users to be able to populate a FillPdf Form right from the entity.
Whoever may create a new entity, should also be allowed to create any generated PDF files right from the entity view. There should be a 'Create' button / tick box to do so. Already created PDFs should be displayed as a field there, too. Whenever an entity is updated, existing PDF files should be updated, too. If the user may delete the entity, a 'Delete' button allows deleting it altogether.
C1.) Fully automatic
I want an associated FillPdf Form to be populated automatically whenever an entity is created or updated. The latest populated PDF file should be displayed as a field on the entity view. Whenever the entity gets updated, the populated PDF should be automatically updated, too, so only the current one remains.
C2.) Fully automatic with revision support
I want an associated FillPdf Form to be populated automatically whenever an entity is created or updated. The latest populated PDF file of a particular entity revision should be displayed as a field on the entity view, possibly together with some metadata. New entity revisions create a new PDF file. However if the entity gets updated without creating a new revision, the populated PDF should be automatically updated, too, so there's a 1:1 relationship between an entity revision and a populated PDF file.
Comment #11
panchoRegardless, I refactored some parts of the code a bit, mainly to avoid repetition.
Furthermore, if there's only a single FillPdfForm to choose from, there's no point in giving the user a choice.
Also, if no PDF file is attached and no FillPdfForm allowed to create a new one, we don't want to display anything, not even the label.
There's one more @todo: The module doesn't play nicely with non-node entities, as in some places node is hardcoded as entity.
Comment #12
panchoMoved all theming from the formatter level to a dedicated theme function, so it may be overwritten. The files are now rendered with a PDF icon, using theme_file_link(), again, this is unless someone overwrites the theme function. Also, there is a "Simple" formatter (no table, no buttons, just links) now for teasers, views etc. Some CSS styling is still missing, though.
Also moved more logic into fillpdf_field_get_fillpdf_forms(), so this security-relevant aspect is centralized in one place.
Comment #13
panchoHere's a slightly larger update that makes our new submodule ready for all kinds of entities.
Successfully tested with the following Core entities: node, user and taxonomy_term.
Not yet tested with Webform, Ubercart or custom entity types, though some generic entity handling code is in place.
We might want to use a hash for creating a unique form ID in theme_fillpdf_field_formatter_table(), just the way Inline Entity Form does, but I've not yet implemented that.
Comment #14
panchoPatch #13 introduces a bug, as with the naming of fillpdf_field_entity_load() and fillpdf_field_entity_save() I was unwillingly running into Core's hook_entity_load() and hook_entity_save(), so I'm renaming these two. Also, I moved all hook implementations to the end of the module file.
Finally I copied over Entity API's generic entity_save() code. Didn't want to make Entity API a prerequisite, as we don't know yet whether the FillPDF Field will end up as a submodule or as part of the main module.
Comment #15
pancho@wizonesolutions proposed requiring Entity API module, which current patch does. This substantially simplifies CRUD operations. I'm removing the Exception handling for custom entity types but keeping the transaction layer, so to roll everything back if saving fails.
As fillpdf_merge_pdf() doesn't produce any useful output for non-node entities without Entity API's Entity Token submodule being installed, too. I'm therefore requiring this one as well.
I'm also keeping the fillpdf_field_pdf_merge() wrapper around fillpdf_merge_pdf() as I don't trust the new $entity_ids parameter to handle nodes exactly the same as if they were passed in using the legacy $nids parameter. This rather new generic entity handling code basically seemed to work, but still needs some refactoring/testing.
Comment #16
wizonesolutionsOK, so to comment on your use cases:
Use cases:
Generally: Permissions should follow FillPDF and entity permissions. So if they are allowed to generate PDFs with the entities they can view, then when they create entities directly, they should also be able to generate the PDF.
I think the "C" group of use cases will be most popular from what I have seen, followed by "B" (especially for admins).
A1: It should be optional to expose these buttons, probably options on the field widget so that they can be changed after creation.
A2: Same comment. We could have a new permission specifically allowing PDF deletion. Entity deletion permissions would override this (because if they can delete the whole entity, they can cause the PDF to be deleted anyway).
A3 (and buttons in general): We should have a "Regenerate" (or similar name) button as well in case anything goes wrong the first time. It can happen.
B1: This should be controlled the same way as the buttons, and probably always available for users with the permission (based on FillPDF permissions for creation/regeneration and entity permissions for deletion).
B2: I think this would happen because of the "A" cases anyway.
C1/C2: Auto-generation probably should be a setting (on by default). It would then happen on entity/webform presave.
Comment #17
panchoThanks for your feedback, Kevin!
It's really helpful to know I'm not the only one who thinks usecase C might be the most important one!
I'm currently underway revamping the module so it more or less covers all of A, B and C usecases. A1/2/3 and C1 are already working. B is trickier than expected, but I'll figure it out and will hopefully post both the next version and a few screenshots/screencasts during the weekend. For now I will leave out C2, which is a good followup feature request.
All in all, this looks like is going to be an awesome tool and a nice showcase of FillPDF's power. :)
Comment #18
wizonesolutionsPancho, is the D7 version of this "done"? Or are you going to make changes?
Comment #19
panchoUnfortunately not at all... :/
I focused on about everything else the last few weeks, so this will definitely need at least a few days for another working patch.
If we want this to be in our upcoming D7 point release, I might however prioritize it vs. other features and tasks.
Comment #20
wizonesolutionsNo rush.
Comment #21
wizonesolutionsComment #22
panchoTo get this going again, I'm posting the latest draft version.
Use-cases A (button on view page) and C1 (automatic generation) are more or less working, with some rough edges still to be fixed. Usecase B (widget on edit form) is not yet implemented.
However, as it currently stands, I simply don't like it. It's on the way to becoming kind of a Leatherman which does too much, so is too complex. However, the original usecase A with the button on the view page introduces a weird logic where a field is edited on the view page, so needs our own permission system.
Currently don't know how to go forward with it, but I think we'll have to tear out this or that, and possibly even start afresh.
Developed the D8 version in parallel, so once we're settled with the big questions, we will implement it in D8 and then backport to D7.
Comment #23
panchoFirst step at simplifying. We don't necessarily need all different field instance settings for the different trigger modes:
Comment #24
panchoWhat I still don't like about it:
1.)
How the generated PDFs are piling up instead of being updated/replaced. I remember you asked this to be optional, @wizonesolutions, so would we add yet another setting?
In this context, I also don't like how field cardinality is handled. If cardinality is reached, the feature obviously ceases attaching more PDFs (though it keeps generating them in automatic mode). I don't think this makes much sense.
I'd rather take the approach that we're always updating/replacing PDFs of the same type, so previous versions get deleted/unpublished. If the host entity is revisionable and we're supporting revisioning, the old version would remain attached to the old vids, so nothing gets lost, it's just not piling up in the current revision.
The only viable alternative I can think of is locking the field to FIELD_CARDINALITY_UNLIMITED, so at least generation doesn't stop working, but then, the PDFs would still pile up.
2.)
Still not sure about permissions.
However: while it doesn't cover all usecases, we could use sane/secure defaults and leave the rest to contrib/custom code.
For automatic mode, this would be: Whoever was allowed to set up automatic generation for this bundle, may also remove generated PDFs on individual entities of that bundle. For more lenient requirements, they'd need custom code.
No it wouldn't override. If the sitebuilder opts for automatic generation, they (and custom code) may expect the PDF to be present on every single entity of that bundle. The user may delete the entity altogether, but may not break that assumption.
For the two other trigger modes (view button and edit widget), this would be: Whoever may edit an entity, may also attach or delete a generated PDF. For stricter requirements, revisioning needs to be turned on. For more lenient requirements, they'd need custom code.
3.)
Now if in quite some case, we're not showing a "Remove" button, the table looks very empty. We should probably show the date/time of generation and allow further customizing it via a view, akin to Viewfield, but without the complexity; if possible, without any additional settings: if Views module is installed, a corresponding view will get created, too, and may be further customized. We'd just need to make all relevant metadata available.
4.)
If revisioning is on, do we create a new revision each time a PDF is generated and attached? I'd say: yes.
But what do we do if there's no revisioning, particularly upon removal? Is the PDF file rightaway deleted or is it just detached? In the former case, the button should say "Delete" rather than "Remove". In the latter case, which would be more consistant between revisioned an non-revisioned entities, how does the admin administrate stale PDF files? Or would we keep them attached but "unpublish" them somehow?
5.)
Where do we store the PDF files? Every FillPDF form brings its own storage scheme and destination_path, yet the field settings allow once more to choose a scheme (and in the D8 version) a destination_path inherited from the File field.
Sure, I can imagine use cases where site builders would like to decide on bundle or field level. Having both options might be nice (and comes for free), but is yet another layer of complexity and source of errors which I’d actually like to avoid. Finally, which setting “wins”?
On the other hand, all File fields allow specifying the storage destination. Do we want to deviate from that?
To make this work Usability-wise, we’d need at least an “override FillPDF form defaults” checkbox, and would use FormAPI #states to otherwise hide the superfluous settings.
Comment #25
panchoThe field doesn't need its own block, as Field as Block module provides everything for that usecase and works fine with FillPDF field. We should however have a horizontally more compact formatter that still supports creating and deleting PDFs.
Furthermore, we should have an exposed filter for the Content view and a bulk action to (re)generate PDFs.
What else do we need to cover reasonable use cases? Please everybody come up with suggestions!
I'm setting this issue to needs review, so it gets a bit exposure and possibly some review/remarks.
Comment #26
panchoJust a minor update to ensure the D7 table schema matches D8 Field API's defaults.
Comment #27
panchoAnd some screenshots:
FillPDF field (table formatter) with generate/remove buttons on the node view page:

FillPDF field (simple formatter) as a block:

Field settings:

Formatter settings:

Comment #28
panchoA few more thoughts:
1. (Re #24-5)
I thought about it again, and tend not to allow overriding the FillPDF settings on an entity/field level. To be consistent, we’d have to allow overriding the file name pattern, too, and then it gets too complicated and seems superfluous in most usecases.
In the future, where all FillPDF files are merged using a FillPDF field, webform element or other UI tools rather than by hitting a path, we might hand control to the fields, elements etc. But for now, it would be too much duplication.
We should make sure there is a way to easily override all settings using custom code, but that should be enough.
2.
In D8, where widgets have their own settings page, we can merge the “automatic mode” into a special case of the “edit widget mode”. On the field settings level, we would configure which FillPDF forms are available. On the widget settings page, we may configure which of them is turned on/off by default or is locked on. The “locked on” ones are automatic, and if there are no others, the widget is altogether hidden.
We can’t really do that in D7 though. It simply wouldn’t work UI-wise, as in D7, any widget settings are merged into the field instance settings page.
3.
We should make sure that custom code using FormAPI #states (or Conditional Fields module) can easily access particular FillPDF forms and turn them on or off. Therefore, even in D7, automatic mode should internally translate to a special case of the “edit widget mode”. Also, we should have #3049646: Use an alphanumeric 'machine_name' primary ID for FillPDF forms, not as a strict prerequisite but as a followup.
4.
At least on the widget, but optionally as well in the table formatter, we’d have to show the ‘admin_title’ which would become a real label then. #3040776: Autocreate 'admin_title' from metadata and mark it required is no hard prerequisite either, but would be helpful. We might want to rename the field into ‘title’ then, so to not indicate it would remain hidden from users.
Comment #29
panchoWorked on the missing usecase B (see list in #10): generating PDF on the edit page.
It is somewhat working now. However, I didn't find a way to have the value of an arbitrary FormAPI checkbox available in Field API fillpdf_field_entity_update(), as it obviously wouldn't get stored with the entity. An extra field wouldn't work either. So instead, I added a "Generate PDF" button, which however isn't a viable solution, as the PDF may only be generated after the entity is saved.
Also, there's an issue with the wrong button being triggered.
The other two modes / usecases are working quite fine.
Further changes:
@todos:
Comment #30
panchoMore robust sample link generation on the field settings page.
Comment #31
panchoWith some input by @wizonesolutions, I figured out most of the outstanding issues!
Re #24-1/29-4, we decided there is no use in collecting outdated versions of the PDF file in the current revision. If people want old versions to be archived, they should turn revisions on. In the current revision, each type of PDF form gets updated/replaced now.
Revisioning works, too.
Re #29-1: The checkboxes on the edit page triggering PDF (re)generation, are working now, too.
So out of the usecases in #10, we're now supporting almost everything, except for the semiautomatic mode (= allow the user to configure automation per entity) or usecase B2. Remembering which PDF files should be re/generated per entity would require yet another field property, which is no major problem, but should IMO be a separate feature request.
Anyway, for UX we're tending to merge all currently separate "modes" into one. Automatic mode would then be just a special case of edit widget mode, with the checkboxes being invisible. This would also allow un/setting checkboxes based on other input / data in a form_alter().
Also refactored the PDF generation code, so for multiple fields x multiple instances, most code only runs once.
There are quite some more UX and DX @todos, but basically this should be ready for testing.
Comment #32
panchoDepending on node type settings, autocreate a new revision as well, when attaching a PDF generated by hitting a button on the view page.
Comment #33
panchoFixed available FillPDF forms in edit widget.
Comment #34
panchoThis patch programmatically integrates automatic mode into the edit widget mode. Doing so allows form_altering the visually hidden checkboxes in automatic mode.
The distinction on the UI however remains in place. Automatic mode essentially means all FillPDF forms marked available in the settings are checked and locked (actually just hidden). We can merge the two modes in the UI, but then we need to select a default on/off value and a locked property to each available FillPDF form. While slightly more powerful, I'm unsure that would be easier to use. But let's see...
Comment #35
panchoMake sure hook_entity_update() resp. hook_entity_insert() only run if hook_field_attach_submit() set FillPDF forms to populate.
Remove the now redundant $entity->fillpdf_op flag. The new $entity->fillpdf_forms approach is better.
Comment #36
panchoFix D7 Core content translation.
D8-style entity translation will follow, once FillPDF field landed in D7, was ported to D8 and translation handling backported to D7.
Comment #37
panchoMore fixes regarding D7 Core content translation.
Comment #38
panchoIn the table formatter, add a "Regenerate" button to each existing PDF, so the generic "Generate PDF" button is only shown for additional FillPdfForms that are available but not yet populated.
Comment #39
panchoTurn operations into a ctools dropbutton:

Comment #40
panchoSimplify extracting FillPDF form IDs to re/generate. We don't have to loop through the fields twice.
Comment #41
panchoLet's provide our own replacement for the behemoth fillpdf_merge_pdf() with all its legacy baggage.
Comment #42
pancho$fidor$form_id:$fidis ambiguous in regard to managed files and FillPDF forms.$form_idis ambiguous in regard to FillPDF forms and Drupal forms.Comment #43
panchoUse file_file_download() for our hook_file_download() implementation, just the way image module does.
Comment #44
panchoSome more minor code cleanup.
Comment #45
panchoMerge in our own version of fillpdf_action_save_to_file() that doesn't create fillpdf_file_context records but registers the host entity with file_usage_add().
Also, renamed fillpdf_field_generate_pdf() to fillpdf_field_attach_pdfs().
@todo: Private file access check needs to be adapted.
Comment #46
panchoTo get private file downloads working again, unfortunately we have to do a couple of things here:
Comment #47
panchoOh, this is the trick: #702586: hook_field_load() and friends are not real hooks
So it's FIELD_NAME_field_load() not MODULE_NAME_field load(). Accordingly, with the field name being just 'fillpdf', we need to implement fillpdf_field_load() not fillpdf_field_field_load().
Comment #48
panchoNow that we're loading the whole file entity into the host entity, we can avoid some additional file_load() calls.
Comment #49
pancho#46 worked, I just missed flushing the cache, as it's a callback rather than a real hook.
So #47 was bull$#!+
#48 makes sense, but only in combination with reverting #47, so here's a new one.
Private file downloads should now be working fine again.
Comment #50
panchoMinor nitpicks.
Comment #51
panchoMajor UX improvement: in edit widget mode, merge table with checkboxes to a custom tableselect element.

This is how it looks like now:
Also unhide description field on the settings page - it is now always shown on the widget, if present, and needs to be customized.
Comment #52
panchoFor those who don't want a big table displayed on the edit form, I'm adding a 'fillpdf_simple' widget that doesn't show more than a minimum.
Comment #53
panchoWe're integrating with Views but currently aren't using Views to provide our own functionality. Allowing to customize our tables and tableselects would be awesome, but particularly for tableselects we'd need a custom views field handler, so should be a followup.
Also, while we're currently using Entity API, we don't necessarily need Entity tokens. As with our parent module FillPDF, if using non-node entities, you won't be able to do much without Entity tokens. But that doesn't mean we have to require it.
CTools we're using for dropbuttons, so this one has to stay in for now.
Comment #54
panchoAt this point, permissions and formatter configuration remain @todos. But apart from that, this should be ready for review.
Assigning to @wizonesolutions for some code review and manual testing on 7.x-1.x.
Comment #55
wizonesolutionsI'm just tagging this but unassigning, as Liam could also do this review. I don't have time to do it right now. I will try next week or during Drupal Dev Days if I have some time.
Comment #56
liam morlandPostponing until #3052463: fillpdf_file_download() overreaches is fixed.
Comment #57
panchoPlain reroll without patch from #3052463: fillpdf_file_download() overreaches. Should work once that one landed.
Comment #58
liam morlandComment #59
panchoI’m porting it to D8 now. We can still backport any changes to the D7 patch.
Comment #60
avpaderno